Compare commits
251 Commits
release/0.
...
T0413-MG-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83495dea32 | ||
|
|
a75f2bfe37 | ||
|
|
d823ac6915 | ||
|
|
35ff0f58fb | ||
|
|
8ec718a700 | ||
|
|
323002e4f9 | ||
|
|
62d5028c89 | ||
|
|
229e24c8d3 | ||
|
|
4a2eade101 | ||
|
|
8db0437659 | ||
|
|
38c42405b2 | ||
|
|
8fd3d194ac | ||
|
|
a86fb85515 | ||
|
|
12240ac356 | ||
|
|
a40e0d81b3 | ||
|
|
30fb3270ed | ||
|
|
473af1a139 | ||
|
|
dfb462a3d4 | ||
|
|
76f1caea77 | ||
|
|
12240b1dff | ||
|
|
c55f2b6d47 | ||
|
|
7a1252d730 | ||
|
|
bc42a09f13 | ||
|
|
eb1f5bfc80 | ||
|
|
8ffb2afad7 | ||
|
|
53fa1482cb | ||
|
|
c5f722b724 | ||
|
|
573b6cb045 | ||
|
|
4afcf1b655 | ||
|
|
4e7c569071 | ||
|
|
ea27ac9391 | ||
|
|
958bc870b3 | ||
|
|
f0382c82cd | ||
|
|
6fab357430 | ||
|
|
f273be63a2 | ||
|
|
9b984ab2f2 | ||
|
|
92bad4f62b | ||
|
|
126c9d697b | ||
|
|
0478e89646 | ||
|
|
814bb66ea6 | ||
|
|
98f83e0c88 | ||
|
|
2e7d3822fe | ||
|
|
0a7d4278b1 | ||
|
|
291158160d | ||
|
|
e525818355 | ||
|
|
0bcc1d67bc | ||
|
|
42ac5d4ea3 | ||
|
|
48587d6d5e | ||
|
|
157590a294 | ||
|
|
7f179dc462 | ||
|
|
594f3ec1b0 | ||
|
|
804a44dd6c | ||
|
|
1b50dc60f9 | ||
|
|
9d6b578237 | ||
|
|
f7f861ca71 | ||
|
|
c12e4a49b1 | ||
|
|
4c0fc11f69 | ||
|
|
e0ffc533b9 | ||
|
|
0dcfdb9b89 | ||
|
|
1d973f7e31 | ||
|
|
4f4903803f | ||
|
|
ff0d5962e1 | ||
|
|
04ceb8d4b1 | ||
|
|
fb7025a716 | ||
|
|
89b6262fa4 | ||
|
|
d2ff465f8e | ||
|
|
c12a87e9ca | ||
|
|
63de0b5db4 | ||
|
|
dd9180da32 | ||
|
|
aaf0c1ca08 | ||
|
|
1513a455de | ||
|
|
4f9e9aeafe | ||
|
|
857de23687 | ||
|
|
fba5d75bf4 | ||
|
|
478acb7934 | ||
|
|
df27b8477d | ||
|
|
02d3a04ffa | ||
|
|
4a53a31fcf | ||
|
|
28590aea53 | ||
|
|
0a2f2bfc90 | ||
|
|
a7b672ebbf | ||
|
|
9831f0396a | ||
|
|
8bfebfca9d | ||
|
|
21cee1eaec | ||
|
|
098333f735 | ||
|
|
3dd393f8eb | ||
|
|
6628d20e5a | ||
|
|
b923d2bc36 | ||
|
|
3932376301 | ||
|
|
d36da12b48 | ||
|
|
a149f5bd81 | ||
|
|
1a8ae7f9c2 | ||
|
|
b510ac1049 | ||
|
|
59bc9d8989 | ||
|
|
b6dfb44b4c | ||
|
|
81ad5110ff | ||
|
|
f92dee6e7b | ||
|
|
f047f55020 | ||
|
|
0c42bedf2f | ||
|
|
7d9f741ceb | ||
|
|
4d2eda398f | ||
|
|
317f7f118c | ||
|
|
f35225f26b | ||
|
|
e04b96a0ac | ||
|
|
21be41995c | ||
|
|
6c69351ea1 | ||
|
|
e9ea8693fa | ||
|
|
9d3f75a34d | ||
|
|
590fc5c9a8 | ||
|
|
1d5952bb9c | ||
|
|
24eb021840 | ||
|
|
ac3560ec8c | ||
|
|
974ce1e9e9 | ||
|
|
66e644551f | ||
|
|
df72723664 | ||
|
|
96bb7a87e3 | ||
|
|
39deba3b9e | ||
|
|
d04993df67 | ||
|
|
7e35798401 | ||
|
|
1d2bb2cda6 | ||
|
|
8fb3a53b78 | ||
|
|
a1b5bdd88f | ||
|
|
3cd89e1fe4 | ||
|
|
90c83de7c2 | ||
|
|
7cd96dc2f5 | ||
|
|
41551bcd36 | ||
|
|
e5b3414335 | ||
|
|
b7738c64b3 | ||
|
|
a8b81fdcde | ||
|
|
9f006c1b57 | ||
|
|
2b8f068ca9 | ||
|
|
d4c2798551 | ||
|
|
4b5068e455 | ||
|
|
44917fdfa6 | ||
|
|
ba55372610 | ||
|
|
008efaf243 | ||
|
|
68a1a2da23 | ||
|
|
7b824ed622 | ||
|
|
5632852890 | ||
|
|
d63eb191f9 | ||
|
|
f1327c52ef | ||
|
|
f48ad62647 | ||
|
|
eaabcd8d0d | ||
|
|
fb4ce6c5ed | ||
|
|
dd5d29fa1d | ||
|
|
963d779d48 | ||
|
|
5695774251 | ||
|
|
181f937c15 | ||
|
|
7ea2d1b638 | ||
|
|
c4a1a6c0b4 | ||
|
|
d0411510fa | ||
|
|
fbdcad1106 | ||
|
|
6f83fff171 | ||
|
|
8363991575 | ||
|
|
1d30a2e5cb | ||
|
|
31a4c55e76 | ||
|
|
456267249e | ||
|
|
32b35f6f88 | ||
|
|
000d6dba55 | ||
|
|
6e9404c89a | ||
|
|
d531e44bef | ||
|
|
74d9dd0b0a | ||
|
|
a456d6cdc0 | ||
|
|
ec0caad44c | ||
|
|
14c4c6d060 | ||
|
|
b922ca75d0 | ||
|
|
f5a94d6e29 | ||
|
|
eed83a210e | ||
|
|
0e95934719 | ||
|
|
565927631f | ||
|
|
64da28ca83 | ||
|
|
4049b9d10a | ||
|
|
bb4e747fd3 | ||
|
|
b6b6403b06 | ||
|
|
ad892f2db3 | ||
|
|
32e56684db | ||
|
|
bfbace8168 | ||
|
|
4e5a91e7fb | ||
|
|
2562070b06 | ||
|
|
e7f363ecbe | ||
|
|
34db077cbd | ||
|
|
47ce444c02 | ||
|
|
e8903ef477 | ||
|
|
d1be5ea136 | ||
|
|
c50b518fad | ||
|
|
dfc546183d | ||
|
|
5953f07be3 | ||
|
|
ad740e4ae2 | ||
|
|
bd0fd2619c | ||
|
|
95c9755e13 | ||
|
|
4669e0ae7d | ||
|
|
6a7b983a58 | ||
|
|
beec3e3f5f | ||
|
|
ffbe5b449d | ||
|
|
0a7de969f3 | ||
|
|
2aa960403a | ||
|
|
d17d1497d1 | ||
|
|
e2e7823ec4 | ||
|
|
ab53cbb0c3 | ||
|
|
90286b8166 | ||
|
|
7128320b36 | ||
|
|
87511460f1 | ||
|
|
3e20afde6e | ||
|
|
29aa9b9e92 | ||
|
|
4281a57923 | ||
|
|
6c422f3208 | ||
|
|
a3229881bf | ||
|
|
c96421424f | ||
|
|
d156d5b41c | ||
|
|
84a6ab75cb | ||
|
|
fd87facf65 | ||
|
|
ec4d8c7bb1 | ||
|
|
cfbfeb1dfc | ||
|
|
015573b5af | ||
|
|
de48548164 | ||
|
|
7d0590a753 | ||
|
|
fd81ebdfe3 | ||
|
|
0c7313bb5f | ||
|
|
c425624ba6 | ||
|
|
0683ed4134 | ||
|
|
6d10d90d98 | ||
|
|
4f16b814c7 | ||
|
|
029a36eab1 | ||
|
|
a1fa7de115 | ||
|
|
98dc7e2849 | ||
|
|
1a50165c22 | ||
|
|
c11d391e62 | ||
|
|
b7a5532cc2 | ||
|
|
1fb5d14751 | ||
|
|
3121f7d89d | ||
|
|
3a88668ace | ||
|
|
cc2160d397 | ||
|
|
c7f69fd861 | ||
|
|
5fefd9d82f | ||
|
|
c3bfd3004b | ||
|
|
9889b9421d | ||
|
|
4776bea221 | ||
|
|
5906258de0 | ||
|
|
79947c376b | ||
|
|
0c7f384fd2 | ||
|
|
2011aea165 | ||
|
|
ba6632a00d | ||
|
|
551c6c5ad6 | ||
|
|
3a0dda976a | ||
|
|
b5e255b896 | ||
|
|
b3d1cd8257 | ||
|
|
d968370c3e | ||
|
|
d910813955 | ||
|
|
591eadad20 | ||
|
|
ec67e71d39 | ||
|
|
0c111d52dc |
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"project_id" : "memgraph",
|
||||
"conduit_uri" : "https://phabricator.memgraph.io",
|
||||
"phabricator_uri" : "https://phabricator.memgraph.io",
|
||||
"git.default-relative-commit": "origin/master",
|
||||
"arc.land.onto.default": "master"
|
||||
}
|
||||
9
.arclint
9
.arclint
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"linters": {
|
||||
"clang-tidy": {
|
||||
"type": "script-and-regex",
|
||||
"script-and-regex.script": "./tools/arc-clang-tidy",
|
||||
"script-and-regex.regex": "/^(?P<file>.*):(?P<line>\\d+):(?P<char>\\d+): (?P<severity>warning|error): (?P<message>.*)$/m"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,5 @@ UseTab: Never
|
||||
DerivePointerAlignment: false
|
||||
PointerAlignment: Right
|
||||
ColumnLimit : 80
|
||||
IncludeBlocks: Preserve
|
||||
...
|
||||
|
||||
@@ -36,6 +36,9 @@ Checks: '*,
|
||||
-hicpp-use-equals-default,
|
||||
-hicpp-vararg,
|
||||
-llvm-header-guard,
|
||||
-llvmlibc-callee-namespace,
|
||||
-llvmlibc-implementation-in-namespace,
|
||||
-llvmlibc-restrict-system-libc-headers,
|
||||
-misc-non-private-member-variables-in-classes,
|
||||
-misc-unused-parameters,
|
||||
-modernize-avoid-c-arrays,
|
||||
|
||||
43
.githooks/pre-commit
Executable file
43
.githooks/pre-commit
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/bin/sh
|
||||
|
||||
project_folder=$(git rev-parse --show-toplevel)
|
||||
if git rev-parse --verify HEAD >/dev/null 2>&1
|
||||
then
|
||||
against=HEAD
|
||||
else
|
||||
# Initial commit: diff against an empty tree object
|
||||
against=$(git hash-object -t tree /dev/null)
|
||||
fi
|
||||
|
||||
# Redirect output to stderr.
|
||||
exec 1>&2
|
||||
|
||||
tmpdir=$(mktemp -d repo-XXXXXXXX)
|
||||
trap "rm -rf $tmpdir" EXIT INT
|
||||
|
||||
modified_files=$(git diff --cached --name-only --diff-filter=AM $against | sed -nE "/.*\.(cpp|cc|cxx|c|h|hpp|lcp)$/p")
|
||||
for file in $modified_files; do
|
||||
echo "Checking $file..."
|
||||
|
||||
cp $project_folder/.clang-format $project_folder/.clang-tidy $tmpdir
|
||||
|
||||
git checkout-index --prefix="$tmpdir/" -- $file
|
||||
|
||||
echo "Running clang-format..."
|
||||
$project_folder/tools/git-clang-format $tmpdir/$file
|
||||
code=$?
|
||||
|
||||
if [ $code -ne 0 ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
echo "Running clang-tidy..."
|
||||
$project_folder/tools/git-clang-tidy $tmpdir/$file
|
||||
code=$?
|
||||
|
||||
if [ $code -ne 0 ]; then
|
||||
break
|
||||
fi
|
||||
done;
|
||||
|
||||
return $code
|
||||
330
.github/workflows/diff.yaml
vendored
Normal file
330
.github/workflows/diff.yaml
vendored
Normal file
@@ -0,0 +1,330 @@
|
||||
name: Diff
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '*.md'
|
||||
|
||||
jobs:
|
||||
community_build:
|
||||
name: "Community build"
|
||||
runs-on: [self-hosted, General, Linux, X64, Debian10]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build community binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build community binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release -DMG_ENTERPRISE=OFF ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Run stress test
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration
|
||||
|
||||
coverage_build:
|
||||
name: "Coverage build"
|
||||
runs-on: [self-hosted, General, Linux, X64, Debian10]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build coverage binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build coverage binaries.
|
||||
cd build
|
||||
cmake -DTEST_COVERAGE=ON ..
|
||||
make -j$THREADS memgraph__unit
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Compute code coverage
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Compute code coverage.
|
||||
cd tools/github
|
||||
./coverage_convert
|
||||
|
||||
# Package code coverage.
|
||||
cd generated
|
||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||
|
||||
- name: Save code coverage
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
|
||||
debug_build:
|
||||
name: "Debug build"
|
||||
runs-on: [self-hosted, General, Linux, X64, Debian10]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build debug binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build debug binaries.
|
||||
cd build
|
||||
cmake ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run leftover CTest tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Run leftover CTest tests (all except unit and benchmark tests).
|
||||
cd build
|
||||
ctest -E "(memgraph__unit|memgraph__benchmark)" --output-on-failure
|
||||
|
||||
- name: Run drivers tests
|
||||
run: |
|
||||
./tests/drivers/run.sh
|
||||
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
cd tests/integration
|
||||
for name in *; do
|
||||
if [ ! -d $name ]; then continue; fi
|
||||
pushd $name >/dev/null
|
||||
echo "Running: $name"
|
||||
if [ -x prepare.sh ]; then
|
||||
./prepare.sh
|
||||
fi
|
||||
if [ -x runner.py ]; then
|
||||
./runner.py
|
||||
elif [ -x runner.sh ]; then
|
||||
./runner.sh
|
||||
fi
|
||||
echo
|
||||
popd >/dev/null
|
||||
done
|
||||
|
||||
- name: Run cppcheck and clang-format
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Run cppcheck and clang-format.
|
||||
cd tools/github
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
|
||||
release_build:
|
||||
name: "Release build"
|
||||
runs-on: [self-hosted, General, Linux, X64, Debian10]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up parent
|
||||
run: |
|
||||
# Remove parent folder (if it exists).
|
||||
cd ..
|
||||
if [ -d parent ]; then
|
||||
rm -rf parent
|
||||
fi
|
||||
|
||||
# Copy untouched repository to parent folder.
|
||||
cp -r memgraph parent
|
||||
|
||||
# Checkout previous commit
|
||||
cd parent
|
||||
git checkout HEAD~1
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build release binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Build parent binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
cd ../parent
|
||||
./init
|
||||
|
||||
# Build parent binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||
make -j$THREADS memgraph memgraph__macro_benchmark
|
||||
|
||||
- name: Run macro benchmark tests
|
||||
run: |
|
||||
cd tests/macro_benchmark
|
||||
./harness QuerySuite MemgraphRunner \
|
||||
--groups aggregation 1000_create unwind_create dense_expand match \
|
||||
--no-strict
|
||||
|
||||
- name: Run parent macro benchmark tests
|
||||
run: |
|
||||
cd ../parent/tests/macro_benchmark
|
||||
./harness QuerySuite MemgraphRunner \
|
||||
--groups aggregation 1000_create unwind_create dense_expand match \
|
||||
--no-strict
|
||||
|
||||
- name: Compute macro benchmark summary
|
||||
run: |
|
||||
./tools/github/macro_benchmark_summary \
|
||||
--current tests/macro_benchmark/.harness_summary \
|
||||
--previous ../parent/tests/macro_benchmark/.harness_summary \
|
||||
--output macro_benchmark_summary.txt
|
||||
|
||||
- name: Save macro benchmark summary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Macro benchmark summary"
|
||||
path: macro_benchmark_summary.txt
|
||||
|
||||
- name: Run GQL Behave tests
|
||||
run: |
|
||||
cd tests/gql_behave
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
tests/gql_behave/gql_behave_status.csv
|
||||
tests/gql_behave/gql_behave_status.html
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration
|
||||
|
||||
- name: Run stress test (SSL)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --use-ssl
|
||||
|
||||
- name: Run durability test
|
||||
run: |
|
||||
cd tests/stress
|
||||
source ve3/bin/activate
|
||||
python3 durability --num-steps 5
|
||||
|
||||
release_jepsen_test:
|
||||
name: "Release Jepsen Test"
|
||||
runs-on: [self-hosted, Linux, X64, Debian10, JepsenControl]
|
||||
#continue-on-error: true
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build only memgraph release binarie.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||
make -j$THREADS memgraph
|
||||
|
||||
- name: Run Jepsen tests
|
||||
run: |
|
||||
cd tests/jepsen
|
||||
./run.sh test --binary ../../build/memgraph --run-args "test-all --node-configs resources/node-config.edn" --ignore-run-stdout-logs --ignore-run-stderr-logs
|
||||
|
||||
- name: Save Jepsen report
|
||||
uses: actions/upload-artifact@v2
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: "Jepsen Report"
|
||||
path: tests/jepsen/Jepsen.tar.gz
|
||||
309
.github/workflows/release.yaml
vendored
Normal file
309
.github/workflows/release.yaml
vendored
Normal file
@@ -0,0 +1,309 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 1 * * *"
|
||||
|
||||
jobs:
|
||||
community_build:
|
||||
name: "Community build"
|
||||
runs-on: [self-hosted, General, Linux, X64, Debian10]
|
||||
env:
|
||||
THREADS: 24
|
||||
timeout-minutes: 960
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build community binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build community binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release -DMG_ENTERPRISE=OFF ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Create community DEB package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Create community DEB package.
|
||||
cd build
|
||||
mkdir output && cd output
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save community DEB package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Community DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration
|
||||
|
||||
- name: Run stress test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --large-dataset
|
||||
|
||||
coverage_build:
|
||||
name: "Coverage build"
|
||||
runs-on: [self-hosted, General, Linux, X64, Debian10]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build coverage binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build coverage binaries.
|
||||
cd build
|
||||
cmake -DTEST_COVERAGE=ON ..
|
||||
make -j$THREADS memgraph__unit
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Compute code coverage
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Compute code coverage.
|
||||
cd tools/github
|
||||
./coverage_convert
|
||||
|
||||
# Package code coverage.
|
||||
cd generated
|
||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||
|
||||
- name: Save code coverage
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
|
||||
debug_build:
|
||||
name: "Debug build"
|
||||
runs-on: [self-hosted, General, Linux, X64, Debian10]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build debug binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build debug binaries.
|
||||
cd build
|
||||
cmake ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run leftover CTest tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Run leftover CTest tests (all except unit and benchmark tests).
|
||||
cd build
|
||||
ctest -E "(memgraph__unit|memgraph__benchmark)" --output-on-failure
|
||||
|
||||
- name: Run drivers tests
|
||||
run: |
|
||||
./tests/drivers/run.sh
|
||||
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
cd tests/integration
|
||||
for name in *; do
|
||||
if [ ! -d $name ]; then continue; fi
|
||||
pushd $name >/dev/null
|
||||
echo "Running: $name"
|
||||
if [ -x prepare.sh ]; then
|
||||
./prepare.sh
|
||||
fi
|
||||
if [ -x runner.py ]; then
|
||||
./runner.py
|
||||
elif [ -x runner.sh ]; then
|
||||
./runner.sh
|
||||
fi
|
||||
echo
|
||||
popd >/dev/null
|
||||
done
|
||||
|
||||
- name: Run cppcheck and clang-format
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Run cppcheck and clang-format.
|
||||
cd tools/github
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
|
||||
release_build:
|
||||
name: "Release build"
|
||||
runs-on: [self-hosted, General, Linux, X64, Debian10]
|
||||
env:
|
||||
THREADS: 24
|
||||
timeout-minutes: 960
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build release binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Create enterprise DEB package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Create enterprise DEB package.
|
||||
cd build
|
||||
mkdir output && cd output
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save enterprise DEB package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Enterprise DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
|
||||
- name: Run micro benchmark tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Run micro benchmark tests.
|
||||
cd build
|
||||
# The `eval` benchmark needs a large stack limit.
|
||||
ulimit -s 262144
|
||||
ctest -R memgraph__benchmark -V
|
||||
|
||||
- name: Run macro benchmark tests
|
||||
run: |
|
||||
cd tests/macro_benchmark
|
||||
./harness QuerySuite MemgraphRunner \
|
||||
--groups aggregation 1000_create unwind_create dense_expand match \
|
||||
--no-strict
|
||||
|
||||
- name: Run parallel macro benchmark tests
|
||||
run: |
|
||||
cd tests/macro_benchmark
|
||||
./harness QueryParallelSuite MemgraphRunner \
|
||||
--groups aggregation_parallel create_parallel bfs_parallel \
|
||||
--num-database-workers 9 --num-clients-workers 30 \
|
||||
--no-strict
|
||||
|
||||
- name: Run GQL Behave tests
|
||||
run: |
|
||||
cd tests/gql_behave
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
tests/gql_behave/gql_behave_status.csv
|
||||
tests/gql_behave/gql_behave_status.html
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration
|
||||
|
||||
- name: Run stress test (SSL)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --use-ssl
|
||||
|
||||
- name: Run stress test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --large-dataset
|
||||
|
||||
- name: Run durability test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
source ve3/bin/activate
|
||||
python3 durability --num-steps 5
|
||||
|
||||
- name: Run durability test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
source ve3/bin/activate
|
||||
python3 durability --num-steps 20
|
||||
308
.github/workflows/release_centos.yaml
vendored
Normal file
308
.github/workflows/release_centos.yaml
vendored
Normal file
@@ -0,0 +1,308 @@
|
||||
name: Release CentOS
|
||||
|
||||
on: [workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
community_build:
|
||||
name: "Community build"
|
||||
runs-on: [self-hosted, General, Linux, X64, CentOS7]
|
||||
env:
|
||||
THREADS: 24
|
||||
timeout-minutes: 960
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build community binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build community binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release -DMG_ENTERPRISE=OFF ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Create community RPM package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Create community RPM package.
|
||||
cd build
|
||||
mkdir output && cd output
|
||||
cpack -G RPM --config ../CPackConfig.cmake
|
||||
rpmlint memgraph*.rpm
|
||||
|
||||
- name: Save community RPM package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Community RPM package"
|
||||
path: build/output/memgraph*.rpm
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration
|
||||
|
||||
- name: Run stress test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --large-dataset
|
||||
|
||||
coverage_build:
|
||||
name: "Coverage build"
|
||||
runs-on: [self-hosted, General, Linux, X64, CentOS7]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build coverage binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build coverage binaries.
|
||||
cd build
|
||||
cmake -DTEST_COVERAGE=ON ..
|
||||
make -j$THREADS memgraph__unit
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Compute code coverage
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Compute code coverage.
|
||||
cd tools/github
|
||||
./coverage_convert
|
||||
|
||||
# Package code coverage.
|
||||
cd generated
|
||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||
|
||||
- name: Save code coverage
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
|
||||
debug_build:
|
||||
name: "Debug build"
|
||||
runs-on: [self-hosted, General, Linux, X64, CentOS7]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build debug binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build debug binaries.
|
||||
cd build
|
||||
cmake ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run leftover CTest tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Run leftover CTest tests (all except unit and benchmark tests).
|
||||
cd build
|
||||
ctest -E "(memgraph__unit|memgraph__benchmark)" --output-on-failure
|
||||
|
||||
- name: Run drivers tests
|
||||
run: |
|
||||
./tests/drivers/run.sh
|
||||
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
cd tests/integration
|
||||
for name in *; do
|
||||
if [ ! -d $name ]; then continue; fi
|
||||
pushd $name >/dev/null
|
||||
echo "Running: $name"
|
||||
if [ -x prepare.sh ]; then
|
||||
./prepare.sh
|
||||
fi
|
||||
if [ -x runner.py ]; then
|
||||
./runner.py
|
||||
elif [ -x runner.sh ]; then
|
||||
./runner.sh
|
||||
fi
|
||||
echo
|
||||
popd >/dev/null
|
||||
done
|
||||
|
||||
- name: Run cppcheck and clang-format
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Run cppcheck and clang-format.
|
||||
cd tools/github
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
|
||||
release_build:
|
||||
name: "Release build"
|
||||
runs-on: [self-hosted, General, Linux, X64, CentOS7]
|
||||
env:
|
||||
THREADS: 24
|
||||
timeout-minutes: 960
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build release binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Create enterprise RPM package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Create enterprise RPM package.
|
||||
cd build
|
||||
mkdir output && cd output
|
||||
cpack -G RPM --config ../CPackConfig.cmake
|
||||
rpmlint memgraph*.rpm
|
||||
|
||||
- name: Save enterprise RPM package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Enterprise RPM package"
|
||||
path: build/output/memgraph*.rpm
|
||||
|
||||
- name: Run micro benchmark tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v2/activate
|
||||
|
||||
# Run micro benchmark tests.
|
||||
cd build
|
||||
# The `eval` benchmark needs a large stack limit.
|
||||
ulimit -s 262144
|
||||
ctest -R memgraph__benchmark -V
|
||||
|
||||
- name: Run macro benchmark tests
|
||||
run: |
|
||||
cd tests/macro_benchmark
|
||||
./harness QuerySuite MemgraphRunner \
|
||||
--groups aggregation 1000_create unwind_create dense_expand match \
|
||||
--no-strict
|
||||
|
||||
- name: Run parallel macro benchmark tests
|
||||
run: |
|
||||
cd tests/macro_benchmark
|
||||
./harness QueryParallelSuite MemgraphRunner \
|
||||
--groups aggregation_parallel create_parallel bfs_parallel \
|
||||
--num-database-workers 9 --num-clients-workers 30 \
|
||||
--no-strict
|
||||
|
||||
- name: Run GQL Behave tests
|
||||
run: |
|
||||
cd tests/gql_behave
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
tests/gql_behave/gql_behave_status.csv
|
||||
tests/gql_behave/gql_behave_status.html
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration
|
||||
|
||||
- name: Run stress test (SSL)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --use-ssl
|
||||
|
||||
- name: Run stress test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --large-dataset
|
||||
|
||||
- name: Run durability test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
source ve3/bin/activate
|
||||
python3 durability --num-steps 5
|
||||
|
||||
- name: Run durability test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
source ve3/bin/activate
|
||||
python3 durability --num-steps 20
|
||||
118
.github/workflows/test_all_workers.yaml
vendored
Normal file
118
.github/workflows/test_all_workers.yaml
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
name: Test All Workers
|
||||
|
||||
on: [workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
HP-DL360G6-1:
|
||||
name: "HP-DL360G6-1"
|
||||
runs-on: [HP-DL360G6-1]
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Check the system
|
||||
run: |
|
||||
source /opt/toolchain-v2/activate
|
||||
./tools/check-build-system
|
||||
HP-DL360G6-2:
|
||||
name: "HP-DL360G6-2"
|
||||
runs-on: [HP-DL360G6-2]
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Check the system
|
||||
run: |
|
||||
source /opt/toolchain-v2/activate
|
||||
./tools/check-build-system
|
||||
HP-DL360G6-3:
|
||||
name: "HP-DL360G6-3"
|
||||
runs-on: [HP-DL360G6-3]
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Check the system
|
||||
run: |
|
||||
source /opt/toolchain-v2/activate
|
||||
./tools/check-build-system
|
||||
docker --version
|
||||
docker ps | grep jepsen
|
||||
|
||||
HP-DL360G6-v2-1:
|
||||
name: "HP-DL360G6-v2-1"
|
||||
runs-on: [HP-DL360G6-v2-1]
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Check the system
|
||||
run: |
|
||||
source /opt/toolchain-v2/activate
|
||||
./tools/check-build-system
|
||||
HP-DL360G6-v2-2:
|
||||
name: "HP-DL360G6-v2-2"
|
||||
runs-on: [HP-DL360G6-v2-2]
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Check the system
|
||||
run: |
|
||||
source /opt/toolchain-v2/activate
|
||||
./tools/check-build-system
|
||||
HP-DL360G6-v2-3:
|
||||
name: "HP-DL360G6-v2-3"
|
||||
runs-on: [HP-DL360G6-v2-3]
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Check the system
|
||||
run: |
|
||||
source /opt/toolchain-v2/activate
|
||||
./tools/check-build-system
|
||||
HP-DL360G6-v2-4:
|
||||
name: "HP-DL360G6-v2-4"
|
||||
runs-on: [HP-DL360G6-v2-4]
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Check the system
|
||||
run: |
|
||||
source /opt/toolchain-v2/activate
|
||||
./tools/check-build-system
|
||||
HP-DL360G6-v2-5:
|
||||
name: "HP-DL360G6-v2-5"
|
||||
runs-on: [HP-DL360G6-v2-5]
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Check the system
|
||||
run: |
|
||||
source /opt/toolchain-v2/activate
|
||||
./tools/check-build-system
|
||||
HP-DL360G6-v2-6:
|
||||
name: "HP-DL360G6-v2-6"
|
||||
runs-on: [HP-DL360G6-v2-6]
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Check the system
|
||||
run: |
|
||||
source /opt/toolchain-v2/activate
|
||||
./tools/check-build-system
|
||||
HP-DL360G6-v2-7:
|
||||
name: "HP-DL360G6-v2-7"
|
||||
runs-on: [HP-DL360G6-v2-7]
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Check the system
|
||||
run: |
|
||||
source /opt/toolchain-v2/activate
|
||||
./tools/check-build-system
|
||||
@@ -1,172 +0,0 @@
|
||||
import os
|
||||
import os.path
|
||||
import fnmatch
|
||||
import logging
|
||||
import ycm_core
|
||||
|
||||
BASE_FLAGS = [
|
||||
'-Wall',
|
||||
'-Wextra',
|
||||
'-Werror',
|
||||
'-Wno-long-long',
|
||||
'-Wno-variadic-macros',
|
||||
'-fexceptions',
|
||||
'-ferror-limit=10000',
|
||||
'-std=c++1z',
|
||||
'-xc++',
|
||||
'-I/usr/lib/',
|
||||
'-I/usr/include/',
|
||||
'-I./src',
|
||||
'-I./include',
|
||||
'-I./libs/fmt',
|
||||
'-I./libs/yaml-cpp',
|
||||
'-I./libs/glog/include',
|
||||
'-I./libs/googletest/googletest/include',
|
||||
'-I./libs/googletest/googlemock/include',
|
||||
'-I./libs/benchmark/include',
|
||||
'-I./libs/cereal/include',
|
||||
# We include cppitertools headers directly from libs directory.
|
||||
'-I./libs',
|
||||
'-I./libs/rapidcheck/include',
|
||||
'-I./libs/antlr4/runtime/Cpp/runtime/src',
|
||||
'-I./libs/gflags/include',
|
||||
'-I./experimental/distributed/src',
|
||||
'-I./libs/postgresql/include',
|
||||
'-I./libs/bzip2',
|
||||
'-I./libs/zlib',
|
||||
'-I./libs/rocksdb/include',
|
||||
'-I./libs/librdkafka/include/librdkafka',
|
||||
'-I./build/include'
|
||||
]
|
||||
|
||||
SOURCE_EXTENSIONS = [
|
||||
'.cpp',
|
||||
'.cxx',
|
||||
'.cc',
|
||||
'.c',
|
||||
'.m',
|
||||
'.mm'
|
||||
]
|
||||
|
||||
HEADER_EXTENSIONS = [
|
||||
'.h',
|
||||
'.hxx',
|
||||
'.hpp',
|
||||
'.hh'
|
||||
]
|
||||
|
||||
# set the working directory of YCMD to be this file
|
||||
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
def IsHeaderFile(filename):
|
||||
extension = os.path.splitext(filename)[1]
|
||||
return extension in HEADER_EXTENSIONS
|
||||
|
||||
def GetCompilationInfoForFile(database, filename):
|
||||
if IsHeaderFile(filename):
|
||||
basename = os.path.splitext(filename)[0]
|
||||
for extension in SOURCE_EXTENSIONS:
|
||||
replacement_file = basename + extension
|
||||
if os.path.exists(replacement_file):
|
||||
compilation_info = database.GetCompilationInfoForFile(replacement_file)
|
||||
if compilation_info.compiler_flags_:
|
||||
return compilation_info
|
||||
return None
|
||||
return database.GetCompilationInfoForFile(filename)
|
||||
|
||||
def FindNearest(path, target):
|
||||
candidate = os.path.join(path, target)
|
||||
if(os.path.isfile(candidate) or os.path.isdir(candidate)):
|
||||
logging.info("Found nearest " + target + " at " + candidate)
|
||||
return candidate;
|
||||
else:
|
||||
parent = os.path.dirname(os.path.abspath(path));
|
||||
if(parent == path):
|
||||
raise RuntimeError("Could not find " + target);
|
||||
return FindNearest(parent, target)
|
||||
|
||||
def MakeRelativePathsInFlagsAbsolute(flags, working_directory):
|
||||
if not working_directory:
|
||||
return list(flags)
|
||||
new_flags = []
|
||||
make_next_absolute = False
|
||||
path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ]
|
||||
for flag in flags:
|
||||
new_flag = flag
|
||||
|
||||
if make_next_absolute:
|
||||
make_next_absolute = False
|
||||
if not flag.startswith('/'):
|
||||
new_flag = os.path.join(working_directory, flag)
|
||||
|
||||
for path_flag in path_flags:
|
||||
if flag == path_flag:
|
||||
make_next_absolute = True
|
||||
break
|
||||
|
||||
if flag.startswith(path_flag):
|
||||
path = flag[ len(path_flag): ]
|
||||
new_flag = path_flag + os.path.join(working_directory, path)
|
||||
break
|
||||
|
||||
if new_flag:
|
||||
new_flags.append(new_flag)
|
||||
return new_flags
|
||||
|
||||
|
||||
def FlagsForClangComplete(root):
|
||||
try:
|
||||
clang_complete_path = FindNearest(root, '.clang_complete')
|
||||
clang_complete_flags = open(clang_complete_path, 'r').read().splitlines()
|
||||
return clang_complete_flags
|
||||
except:
|
||||
return None
|
||||
|
||||
def FlagsForInclude(root):
|
||||
try:
|
||||
include_path = FindNearest(root, 'include')
|
||||
flags = []
|
||||
for dirroot, dirnames, filenames in os.walk(include_path):
|
||||
for dir_path in dirnames:
|
||||
real_path = os.path.join(dirroot, dir_path)
|
||||
flags = flags + ["-I" + real_path]
|
||||
return flags
|
||||
except:
|
||||
return None
|
||||
|
||||
def FlagsForCompilationDatabase(root, filename):
|
||||
try:
|
||||
compilation_db_path = FindNearest(root, 'compile_commands.json')
|
||||
compilation_db_dir = os.path.dirname(compilation_db_path)
|
||||
logging.info("Set compilation database directory to " + compilation_db_dir)
|
||||
compilation_db = ycm_core.CompilationDatabase(compilation_db_dir)
|
||||
if not compilation_db:
|
||||
logging.info("Compilation database file found but unable to load")
|
||||
return None
|
||||
compilation_info = GetCompilationInfoForFile(compilation_db, filename)
|
||||
if not compilation_info:
|
||||
logging.info("No compilation info for " + filename + " in compilation database")
|
||||
return None
|
||||
return MakeRelativePathsInFlagsAbsolute(
|
||||
compilation_info.compiler_flags_,
|
||||
compilation_info.compiler_working_dir_)
|
||||
except:
|
||||
return None
|
||||
|
||||
def FlagsForFile(filename):
|
||||
root = os.path.realpath(filename);
|
||||
compilation_db_flags = FlagsForCompilationDatabase(root, filename)
|
||||
if compilation_db_flags:
|
||||
final_flags = compilation_db_flags
|
||||
else:
|
||||
final_flags = BASE_FLAGS
|
||||
clang_flags = FlagsForClangComplete(root)
|
||||
if clang_flags:
|
||||
final_flags = final_flags + clang_flags
|
||||
include_flags = FlagsForInclude(root)
|
||||
if include_flags:
|
||||
final_flags = final_flags + include_flags
|
||||
return {
|
||||
'flags': final_flags,
|
||||
'do_cache': True
|
||||
}
|
||||
124
CHANGELOG.md
124
CHANGELOG.md
@@ -1,5 +1,129 @@
|
||||
# Change Log
|
||||
|
||||
## Future
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Added extra information in durability files to support replication making
|
||||
it incompatible with the durability files generated by older versions of
|
||||
Memgraph. Even though the replication is an Enterprise feature, the files
|
||||
are compatible with the Community version.
|
||||
|
||||
## v1.2.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* SSL is disabled by default (`--bolt-cert-file` and `--bolt-key-file` are
|
||||
empty). This change might only affect the client connection configuration.
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* Added support for Bolt v4.0 and v4.1.
|
||||
* Added `mgp_networkx.py` as an alternative implementation of NetworkX graph
|
||||
objects, which is useful to use Memgraph data from NetworkX algorithms
|
||||
optimally.
|
||||
* Added `nxalg.py` query module as a proxy to NetworkX algorithms.
|
||||
* Added plan optimization to use a label-property index where the property is
|
||||
not null. As a result, the query engine, instead of scanning all elements and
|
||||
applying the filter, performs a label-property index lookup when possible.
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
* Fixed Cypher `ID` function `Null` handling. When the `ID` function receives
|
||||
`Null`, it will also return `Null`.
|
||||
* Fixed bug that caused random crashes in SSL communication on platforms
|
||||
that use older versions of OpenSSL (< 1.1) by adding proper multi-threading
|
||||
handling.
|
||||
* Fix `DISCARD` message handling. The query is now executed before discarding
|
||||
the results.
|
||||
|
||||
## v1.1.0
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* Properties in nodes and edges are now stored encoded and compressed. This
|
||||
change significantly reduces memory usage. Depending on the specific dataset,
|
||||
total memory usage can be reduced up to 50%.
|
||||
* Added support for rescanning query modules. Previously, the query modules
|
||||
directory was scanned only upon startup. Now it is scanned each time the user
|
||||
requests to load a query module. The functions used to load the query modules
|
||||
were renamed to `mg.load()` and `mg.load_all()` (from `mg.reload()` and
|
||||
`mg.reload_all()`).
|
||||
* Improved execution performance of queries that have an IN list filter by
|
||||
using label+property indices.
|
||||
Example: `MATCH (n:Label) WHERE n.property IN [] ...`
|
||||
* Added support for `ANY` and `NONE` openCypher functions. Previously, only
|
||||
`ALL` and `SINGLE` functions were implemented.
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
* Fixed invalid paths returned by variable expansion when the starting node and
|
||||
destination node used the same symbol.
|
||||
Example: `MATCH path = (n:Person {name: "John"})-[:KNOWS*]->(n) RETURN path`
|
||||
* Improved semantics of `ALL` and `SINGLE` functions to be consistent with
|
||||
openCypher when handling lists with `Null`s.
|
||||
* `SHOW CONSTRAINT INFO` now returns property names as a list for unique
|
||||
constraints.
|
||||
* Escaped label/property/edgetype names in `DUMP DATABASE` to support names
|
||||
with spaces in them.
|
||||
* Fixed handling of `DUMP DATABASE` queries in multi-command transactions
|
||||
(`BEGIN`, ..., `COMMIT`).
|
||||
* Fixed handling of various query types in explicit transactions. For example,
|
||||
constraints were allowed to be created in multi-command transactions
|
||||
(`BEGIN`, ..., `COMMIT`) but that isn't a transactional operation and as such
|
||||
can't be allowed in multi-command transactions.
|
||||
* Fixed integer overflow bugs in `COUNT`, `LIMIT` and `SKIP`.
|
||||
* Fixed integer overflow bugs in weighted shortest path expansions.
|
||||
* Fixed various other integer overflow bugs in query execution.
|
||||
* Added Marvel Comic Universe tutorial.
|
||||
* Added FootballTransfers tutorial.
|
||||
|
||||
## v1.0.0
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* [Enterprise Ed.] Exposed authentication username/rolename regex as a flag
|
||||
(`--auth-user-or-role-name-regex`).
|
||||
* [Enterprise Ed.] Improved auth module error handling and added support for
|
||||
relative paths.
|
||||
* Added support for Python query modules. This release of Memgraph supports
|
||||
query modules written using the already existing C API and the new Python
|
||||
API.
|
||||
* Added support for unique constraints. The unique constraint is created with a
|
||||
label and one or more properties.
|
||||
* Implemented support for importing CSV files (`mg_import_csv`). The importer
|
||||
is compatible with the Neo4j batch CSV importer.
|
||||
* Snapshot and write-ahead log format changed (backward compatible with v0.50).
|
||||
* Vertices looked up by their openCypher ID (`MATCH (n) WHERE ID(n) = ...`)
|
||||
will now find the node in O(logn) instead of O(n).
|
||||
* Improved planning of BFS expansion, a faster, specific approach is now
|
||||
favored instead of a ScanAll+Filter operation.
|
||||
* Added syntax for limiting memory of `CALL`.
|
||||
* Exposed server name that should be used for Bolt handshake as flag
|
||||
(`--bolt-server-name-for-init`).
|
||||
* Added several more functions to the query module C API.
|
||||
* Implemented a storage locking mechanism that prevents the user from
|
||||
concurrently starting two Memgraph instances with the same data directory.
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
* [Enterprise Ed.] Fixed a bug that crashed the database when granting
|
||||
privileges to a user.
|
||||
* [Enterprise Ed.] Improved Louvain algorithm for community detection.
|
||||
* Type of variable expansion is now printed in `EXPLAIN` (e.g. ExpandVariable,
|
||||
STShortestPath, BFSExpand, WeightedShortestPath).
|
||||
* Correctly display `CALL` in `EXPLAIN` output.
|
||||
* Correctly delimit arguments when printing the signature of a query module.
|
||||
* Fixed a planning issue when `CALL` preceded filtering.
|
||||
* Fixed spelling mistakes in the storage durability module.
|
||||
* Fixed storage GC indices/constraints subtle race condition.
|
||||
* Reduced memory allocations in storage API and indices.
|
||||
* Memgraph version is now outputted to `stdout` when Memgraph is started.
|
||||
* Improved RPM packaging.
|
||||
* Reduced number of errors reported in production log when loading query
|
||||
modules.
|
||||
* Removed `early access` wording from the Community Offering license.
|
||||
|
||||
## v0.50.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
250
CMakeLists.txt
250
CMakeLists.txt
@@ -35,16 +35,97 @@ else()
|
||||
message(FATAL_ERROR "Couldn't find clang and/or clang++!")
|
||||
endif()
|
||||
|
||||
# Get current commit hash.
|
||||
execute_process(
|
||||
OUTPUT_VARIABLE COMMIT_HASH
|
||||
COMMAND git rev-parse --short HEAD
|
||||
)
|
||||
string(STRIP ${COMMIT_HASH} COMMIT_HASH)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
project(memgraph VERSION 0.50.0)
|
||||
project(memgraph)
|
||||
|
||||
# For more information about how to release a new version of Memgraph, see
|
||||
# `release/README.md`.
|
||||
|
||||
# Option that is used to specify which version of Memgraph should be built. The
|
||||
# default is `ON` which causes the build system to build Memgraph Enterprise.
|
||||
# Memgraph Community is built if explicitly set to `OFF`.
|
||||
option(MG_ENTERPRISE "Build Memgraph Enterprise Edition" ON)
|
||||
|
||||
# Set the current version here to override the automatic version detection. The
|
||||
# version must be specified as `X.Y.Z`. Primarily used when building new patch
|
||||
# versions.
|
||||
set(MEMGRAPH_OVERRIDE_VERSION "")
|
||||
|
||||
# Custom suffix that this version should have. The suffix can be any arbitrary
|
||||
# string. Primarily used when building a version for a specific customer.
|
||||
set(MEMGRAPH_OVERRIDE_VERSION_SUFFIX "")
|
||||
|
||||
# Variables used to generate the versions.
|
||||
if (MG_ENTERPRISE)
|
||||
set(get_version_enterprise "--enterprise")
|
||||
else()
|
||||
set(get_version_enterprise "")
|
||||
endif()
|
||||
set(get_version_script "${CMAKE_SOURCE_DIR}/release/get_version.py")
|
||||
|
||||
# Get version that should be used in the binary.
|
||||
execute_process(
|
||||
OUTPUT_VARIABLE MEMGRAPH_VERSION
|
||||
COMMAND "${get_version_script}" ${get_version_enterprise}
|
||||
"${MEMGRAPH_OVERRIDE_VERSION}"
|
||||
"${MEMGRAPH_OVERRIDE_VERSION_SUFFIX}"
|
||||
)
|
||||
|
||||
# Get version that should be used in the DEB package.
|
||||
execute_process(
|
||||
OUTPUT_VARIABLE MEMGRAPH_VERSION_DEB
|
||||
COMMAND "${get_version_script}" ${get_version_enterprise}
|
||||
--variant deb
|
||||
"${MEMGRAPH_OVERRIDE_VERSION}"
|
||||
"${MEMGRAPH_OVERRIDE_VERSION_SUFFIX}"
|
||||
)
|
||||
|
||||
# Get version that should be used in the RPM package.
|
||||
execute_process(
|
||||
OUTPUT_VARIABLE MEMGRAPH_VERSION_RPM
|
||||
COMMAND "${get_version_script}" ${get_version_enterprise}
|
||||
--variant rpm
|
||||
"${MEMGRAPH_OVERRIDE_VERSION}"
|
||||
"${MEMGRAPH_OVERRIDE_VERSION_SUFFIX}"
|
||||
)
|
||||
|
||||
# We want the above variables to be updated each time something is committed to
|
||||
# the repository. That is why we include a dependency on the current git HEAD
|
||||
# to trigger a new CMake run when the git repository state changes. This is a
|
||||
# hack, as CMake doesn't have a mechanism to regenerate variables when
|
||||
# something changes (only files can be regenerated).
|
||||
# https://cmake.org/pipermail/cmake/2018-October/068389.html
|
||||
#
|
||||
# The hack in the above link is nearly correct but it has a fatal flaw. The
|
||||
# `CMAKE_CONFIGURE_DEPENDS` isn't a `GLOBAL` property, it is instead a
|
||||
# `DIRECTORY` property and as such must be set in the `DIRECTORY` scope.
|
||||
# https://cmake.org/cmake/help/v3.14/manual/cmake-properties.7.html
|
||||
#
|
||||
# Unlike the above mentioned hack, we don't use the `.git/index` file. That
|
||||
# file changes on every `git add` (even on `git status`) so it triggers
|
||||
# unnecessary recalculations of the release version. The release version only
|
||||
# changes on every `git commit` or `git checkout`. That is why we watch the
|
||||
# following files for changes:
|
||||
# - `.git/HEAD` -> changes each time a `git checkout` is issued
|
||||
# - `.git/refs/heads/...` -> the value in `.git/HEAD` is a branch name (when
|
||||
# you are on a branch) and you have to monitor the file of the specific
|
||||
# branch to detect when a `git commit` was issued
|
||||
# More details about the contents of the `.git` directory and the specific
|
||||
# files used can be seen here:
|
||||
# https://git-scm.com/book/en/v2/Git-Internals-Git-References
|
||||
set(git_directory "${CMAKE_SOURCE_DIR}/.git")
|
||||
if (EXISTS "${git_directory}")
|
||||
set_property(DIRECTORY APPEND PROPERTY
|
||||
CMAKE_CONFIGURE_DEPENDS "${git_directory}/HEAD")
|
||||
file(STRINGS "${git_directory}/HEAD" git_head_data)
|
||||
if (git_head_data MATCHES "^ref: ")
|
||||
string(SUBSTRING "${git_head_data}" 5 -1 git_head_ref)
|
||||
set_property(DIRECTORY APPEND PROPERTY
|
||||
CMAKE_CONFIGURE_DEPENDS "${git_directory}/${git_head_ref}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# setup CMake module path, defines path for include() and find_package()
|
||||
@@ -68,20 +149,23 @@ add_custom_target(clean_all
|
||||
# is easier debugging of compilation and linker flags.
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
# c99-designator is disabled because of required mixture of designated and
|
||||
# non-designated initializers in Python Query Module code (`py_module.cpp`).
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall \
|
||||
-Werror=switch -Werror=switch-bool -Werror=return-type \
|
||||
-Werror=return-stack-address")
|
||||
-Werror=return-stack-address \
|
||||
-Wno-c99-designator")
|
||||
|
||||
# Don't omit frame pointer in RelWithDebInfo, for additional callchain debug.
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO
|
||||
"${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -fno-omit-frame-pointer")
|
||||
|
||||
# Statically link libgcc and libstdc++, the GCC allows this according to:
|
||||
# https://gcc.gnu.org/onlinedocs/gcc-8.3.0/libstdc++/manual/manual/license.html
|
||||
# https://gcc.gnu.org/onlinedocs/gcc-10.2.0/libstdc++/manual/manual/license.html
|
||||
# https://www.gnu.org/licenses/gcc-exception-faq.html
|
||||
# Last checked for gcc-8.3 which we are using on the build machines.
|
||||
# Last checked for gcc-10.2 which we are using on the build machines.
|
||||
# ** If we change versions, recheck this! **
|
||||
# ** Static linking is allowed only for executables! **
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++")
|
||||
@@ -107,13 +191,6 @@ else()
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-g")
|
||||
endif()
|
||||
|
||||
# ndebug
|
||||
option(NDEBUG "No debug" OFF)
|
||||
message(STATUS "NDEBUG: ${NDEBUG} (be careful CMAKE_BUILD_TYPE can also \
|
||||
append this flag)")
|
||||
if(NDEBUG)
|
||||
add_definitions( -DNDEBUG )
|
||||
endif()
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# default build type is debug
|
||||
@@ -127,11 +204,6 @@ message(STATUS "CMake build type: ${CMAKE_BUILD_TYPE}")
|
||||
|
||||
# threading
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
# optional Ltalloc
|
||||
option(USE_LTALLOC "Use Ltalloc instead of default allocator (default OFF). \
|
||||
Set this to ON to link with Ltalloc." OFF)
|
||||
|
||||
# optional readline
|
||||
option(USE_READLINE "Use GNU Readline library if available (default ON). \
|
||||
Set this to OFF to prevent linking with Readline even if it is available." ON)
|
||||
@@ -142,68 +214,16 @@ if (USE_READLINE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# OpenSSL
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
set(libs_dir ${CMAKE_SOURCE_DIR}/libs)
|
||||
add_subdirectory(libs EXCLUDE_FROM_ALL)
|
||||
|
||||
include_directories(SYSTEM ${GFLAGS_INCLUDE_DIR})
|
||||
include_directories(SYSTEM ${GLOG_INCLUDE_DIR})
|
||||
include_directories(SYSTEM ${FMT_INCLUDE_DIR})
|
||||
include_directories(SYSTEM ${ANTLR4_INCLUDE_DIR})
|
||||
include_directories(SYSTEM ${BZIP2_INCLUDE_DIR})
|
||||
include_directories(SYSTEM ${ZLIB_INCLUDE_DIR})
|
||||
include_directories(SYSTEM ${ROCKSDB_INCLUDE_DIR})
|
||||
include_directories(SYSTEM ${LIBRDKAFKA_INCLUDE_DIR})
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# openCypher parser -----------------------------------------------------------
|
||||
set(opencypher_frontend ${CMAKE_SOURCE_DIR}/src/query/frontend/opencypher)
|
||||
set(opencypher_generated ${opencypher_frontend}/generated)
|
||||
set(opencypher_lexer_grammar ${opencypher_frontend}/grammar/MemgraphCypherLexer.g4)
|
||||
set(opencypher_parser_grammar ${opencypher_frontend}/grammar/MemgraphCypher.g4)
|
||||
|
||||
# enumerate all files that are generated from antlr
|
||||
set(antlr_opencypher_generated_src
|
||||
${opencypher_generated}/MemgraphCypherLexer.cpp
|
||||
${opencypher_generated}/MemgraphCypher.cpp
|
||||
${opencypher_generated}/MemgraphCypherBaseVisitor.cpp
|
||||
${opencypher_generated}/MemgraphCypherVisitor.cpp
|
||||
)
|
||||
|
||||
# Provide a command to generate sources if missing. If this were a
|
||||
# custom_target, it would always run and we don't want that.
|
||||
add_custom_command(OUTPUT ${antlr_opencypher_generated_src}
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E make_directory ${opencypher_generated}
|
||||
COMMAND
|
||||
java -jar ${CMAKE_SOURCE_DIR}/libs/antlr-4.6-complete.jar -Dlanguage=Cpp -visitor -o ${opencypher_generated} -package antlropencypher ${opencypher_lexer_grammar} ${opencypher_parser_grammar}
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
DEPENDS ${opencypher_lexer_grammar} ${opencypher_parser_grammar}
|
||||
${opencypher_frontend}/grammar/CypherLexer.g4
|
||||
${opencypher_frontend}/grammar/Cypher.g4)
|
||||
|
||||
# add custom target for generation
|
||||
add_custom_target(generate_opencypher_parser
|
||||
DEPENDS ${antlr_opencypher_generated_src})
|
||||
|
||||
add_library(antlr_opencypher_parser_lib STATIC ${antlr_opencypher_generated_src})
|
||||
target_link_libraries(antlr_opencypher_parser_lib antlr4)
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Optional subproject configuration -------------------------------------------
|
||||
option(POC "Build proof of concept binaries" OFF)
|
||||
option(EXPERIMENTAL "Build experimental binaries" OFF)
|
||||
option(CUSTOMERS "Build customer binaries" OFF)
|
||||
option(TEST_COVERAGE "Generate coverage reports from running memgraph" OFF)
|
||||
option(TOOLS "Build tools binaries" ON)
|
||||
option(QUERY_MODULES "Build query modules containing custom procedures" ON)
|
||||
option(MG_COMMUNITY "Build Memgraph Community Edition" OFF)
|
||||
option(ASAN "Build with Address Sanitizer. To get a reasonable performance option should be used only in Release or RelWithDebInfo build " OFF)
|
||||
option(TSAN "Build with Thread Sanitizer. To get a reasonable performance option should be used only in Release or RelWithDebInfo build " OFF)
|
||||
option(UBSAN "Build with Undefined Behaviour Sanitizer" OFF)
|
||||
option(THIN_LTO "Build with link time optimization" OFF)
|
||||
|
||||
if (TEST_COVERAGE)
|
||||
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
|
||||
@@ -214,8 +234,8 @@ if (TEST_COVERAGE)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-instr-generate -fcoverage-mapping")
|
||||
endif()
|
||||
|
||||
if (MG_COMMUNITY)
|
||||
add_definitions(-DMG_COMMUNITY)
|
||||
if (MG_ENTERPRISE)
|
||||
add_definitions(-DMG_ENTERPRISE)
|
||||
endif()
|
||||
|
||||
if (ASAN)
|
||||
@@ -269,26 +289,12 @@ if (UBSAN)
|
||||
# Make sure llvm-symbolizer binary is in path
|
||||
endif()
|
||||
|
||||
if (THIN_LTO)
|
||||
set(CMAKE_CXX_FLAGS"${CMAKE_CXX_FLAGS} -flto=thin")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -flto=thin")
|
||||
endif()
|
||||
|
||||
# Add subprojects
|
||||
include_directories(src)
|
||||
add_subdirectory(src)
|
||||
|
||||
if(POC)
|
||||
add_subdirectory(poc)
|
||||
endif()
|
||||
|
||||
if(EXPERIMENTAL)
|
||||
add_subdirectory(experimental)
|
||||
endif()
|
||||
|
||||
if(CUSTOMERS)
|
||||
add_subdirectory(customers)
|
||||
endif()
|
||||
# Release configuration
|
||||
add_subdirectory(release)
|
||||
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
@@ -300,59 +306,3 @@ endif()
|
||||
if(QUERY_MODULES)
|
||||
add_subdirectory(query_modules)
|
||||
endif()
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# ---- Setup CPack --------
|
||||
# General setup
|
||||
set(CPACK_PACKAGE_NAME memgraph)
|
||||
set(CPACK_PACKAGE_VENDOR "Memgraph Ltd.")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY
|
||||
"High performance, in-memory, transactional graph database")
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR ${memgraph_VERSION_MAJOR})
|
||||
set(CPACK_PACKAGE_VERSION_MINOR ${memgraph_VERSION_MINOR})
|
||||
set(CPACK_PACKAGE_VERSION_PATCH ${memgraph_VERSION_PATCH})
|
||||
set(CPACK_PACKAGE_VERSION_TWEAK ${memgraph_VERSION_TWEAK})
|
||||
set(CPACK_PACKAGE_FILE_NAME ${CPACK_PACKAGE_NAME}-${memgraph_VERSION}-${COMMIT_HASH}${CPACK_SYSTEM_NAME})
|
||||
|
||||
# DEB specific
|
||||
# Instead of using "name <email>" format, we use "email (name)" to prevent
|
||||
# errors due to full stop, '.' at the end of "Ltd". (See: RFC 822)
|
||||
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "tech@memgraph.com (Memgraph Ltd.)")
|
||||
set(CPACK_DEBIAN_PACKAGE_SECTION non-free/database)
|
||||
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE https://memgraph.com)
|
||||
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA
|
||||
"${CMAKE_SOURCE_DIR}/release/debian/conffiles;"
|
||||
"${CMAKE_SOURCE_DIR}/release/debian/copyright;"
|
||||
"${CMAKE_SOURCE_DIR}/release/debian/prerm;"
|
||||
"${CMAKE_SOURCE_DIR}/release/debian/postrm;"
|
||||
"${CMAKE_SOURCE_DIR}/release/debian/postinst;")
|
||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
|
||||
# Description formatting is important, summary must be followed with a newline and 1 space.
|
||||
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}
|
||||
Contains Memgraph, the graph database. It aims to deliver developers the
|
||||
speed, simplicity and scale required to build the next generation of
|
||||
applications driver by real-time connected data.")
|
||||
# Add `openssl` package to dependencies list. Used to generate SSL certificates.
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "openssl (>= 1.1.0)")
|
||||
|
||||
# RPM specific
|
||||
set(CPACK_RPM_PACKAGE_URL https://memgraph.com)
|
||||
set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION
|
||||
/var /var/lib /var/log /etc/logrotate.d
|
||||
/lib /lib/systemd /lib/systemd/system /lib/systemd/system/memgraph.service)
|
||||
set(CPACK_RPM_PACKAGE_REQUIRES_PRE "shadow-utils")
|
||||
# NOTE: user specfile has a bug in cmake 3.7.2, this needs to be patched
|
||||
# manually in: ~/cmake/share/cmake-3.7/Modules/CPackRPM.cmake line 2273
|
||||
# Or newer cmake version used
|
||||
set(CPACK_RPM_USER_BINARY_SPECFILE "${CMAKE_SOURCE_DIR}/release/rpm/memgraph.spec.in")
|
||||
# Description formatting is important, no line must be greater than 80 characters.
|
||||
set(CPACK_RPM_PACKAGE_DESCRIPTION "Contains Memgraph, the graph database.
|
||||
It aims to deliver developers the speed, simplicity and scale required to build
|
||||
the next generation of applications driver by real-time connected data.")
|
||||
# Add `openssl` package to dependencies list. Used to generate SSL certificates.
|
||||
set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.0.0, curl >= 7.29.0")
|
||||
|
||||
# All variables must be set before including.
|
||||
include(CPack)
|
||||
# ---- End Setup CPack ----
|
||||
|
||||
4
CODEOWNERS
Normal file
4
CODEOWNERS
Normal file
@@ -0,0 +1,4 @@
|
||||
/docs/ @gitbuda
|
||||
/src/communication/ @antonio2368
|
||||
/src/query/ @the-joksim
|
||||
/src/storage/ @antonio2368
|
||||
3
Doxyfile
3
Doxyfile
@@ -51,7 +51,7 @@ PROJECT_BRIEF = "The World's Most Powerful Graph Database"
|
||||
# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
|
||||
# the logo to the output directory.
|
||||
|
||||
PROJECT_LOGO = Doxylogo.png
|
||||
PROJECT_LOGO = docs/doxygen/memgraph_logo.png
|
||||
|
||||
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
|
||||
# into which the generated documentation will be written. If a relative path is
|
||||
@@ -839,7 +839,6 @@ EXCLUDE_PATTERNS += */Testing/*
|
||||
EXCLUDE_PATTERNS += */tests/*
|
||||
EXCLUDE_PATTERNS += */dist/*
|
||||
EXCLUDE_PATTERNS += */tools/*
|
||||
EXCLUDE_PATTERNS += */customers/*
|
||||
|
||||
# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
|
||||
# (namespaces, classes, functions, etc.) that should be excluded from the
|
||||
|
||||
55
README.md
55
README.md
@@ -1,24 +1,47 @@
|
||||
# memgraph
|
||||
# Memgraph
|
||||
|
||||
Memgraph is an ACID compliant high performance transactional distributed
|
||||
in-memory graph database featuring runtime native query compiling, lock free
|
||||
data structures, multi-version concurrency control and asynchronous IO.
|
||||
|
||||
## dependencies
|
||||
## Development Documentation
|
||||
|
||||
Memgraph can be compiled using any modern c++ compiler. It mostly relies on
|
||||
the standard template library, however, some things do require external
|
||||
libraries.
|
||||
* [Quick Start](docs/dev/quick-start.md)
|
||||
* [Workflow](docs/dev/workflow.md)
|
||||
* [Storage](docs/dev/storage/v2/contents.md)
|
||||
* [Query Engine](docs/dev/query/contents.md)
|
||||
* [Communication](docs/dev/communication/contents.md)
|
||||
* [Lisp C++ Preprocessor (LCP)](docs/dev/lcp.md)
|
||||
|
||||
Some code contains linux-specific libraries and the build is only supported
|
||||
on a 64 bit linux kernel.
|
||||
## Feature Specifications
|
||||
|
||||
* linux
|
||||
* clang 3.8 (good c++11 support, especially lock free atomics)
|
||||
* antlr (compiler frontend)
|
||||
* cppitertools
|
||||
* fmt format
|
||||
* google benchmark
|
||||
* google test
|
||||
* glog
|
||||
* gflags
|
||||
Each prominent Memgraph feature requires a feature specification. The purpose
|
||||
of the feature specification is to have a base for discussing all aspects of
|
||||
the feature. Elements of feature specifications should be:
|
||||
|
||||
* High-level context.
|
||||
* Interface.
|
||||
* User stories. Usage from the end-user perspective. In the case of a library,
|
||||
that should be cases on how to use the programming interface. In the case of
|
||||
a shell script, that should be cases on how to use flags.
|
||||
* Discussion about concurrency, memory management, error management.
|
||||
* Any other essential functional or non-functional requirements.
|
||||
* Test and benchmark strategy.
|
||||
* Possible future changes/improvements/extensions.
|
||||
* Security concerns.
|
||||
* Additional and/or optional implementation details.
|
||||
|
||||
It's crucial to keep feature spec up-to-date with the implementation. Take a
|
||||
look at the list of [feature specifications](docs/feature_spec/contents.md) to
|
||||
learn more about powerful Memgraph features.
|
||||
|
||||
## User Documentation
|
||||
|
||||
Memgraph user documentation is maintained within
|
||||
[docs](https://github.com/memgraph/docs) repository. The documentation is also
|
||||
available on [GitBook](https://docs.memgraph.com).
|
||||
|
||||
## Licences
|
||||
|
||||
* [Community](release/LICENSE_COMMUNITY.md)
|
||||
* [Enterprise](release/LICENSE_ENTERPRISE.md)
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# paths
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
BUILD_OUTPUT_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "build_release", "output"))
|
||||
|
||||
# helpers
|
||||
def run_cmd(cmd, cwd):
|
||||
return subprocess.run(cmd, cwd=cwd, check=True,
|
||||
stdout=subprocess.PIPE).stdout.decode("utf-8")
|
||||
|
||||
# check project
|
||||
if re.search(r"release", os.environ.get("PROJECT", "")) is None:
|
||||
print(json.dumps([]))
|
||||
sys.exit(0)
|
||||
|
||||
# generate archive
|
||||
deb_name = run_cmd(["find", ".", "-maxdepth", "1", "-type", "f",
|
||||
"-name", "memgraph*.deb"], BUILD_OUTPUT_DIR).split("\n")[0][2:]
|
||||
arch = run_cmd(["dpkg", "--print-architecture"], BUILD_OUTPUT_DIR).split("\n")[0]
|
||||
version = deb_name.split("-")[1]
|
||||
# generate Debian package file name as expected by Debian Policy
|
||||
standard_deb_name = "memgraph_{}-1_{}.deb".format(version, arch)
|
||||
|
||||
archive_path = os.path.relpath(os.path.join(BUILD_OUTPUT_DIR,
|
||||
deb_name), SCRIPT_DIR)
|
||||
|
||||
archives = [{
|
||||
"name": "Release (deb package)",
|
||||
"archive": archive_path,
|
||||
"filename": standard_deb_name,
|
||||
}]
|
||||
|
||||
print(json.dumps(archives, indent=4, sort_keys=True))
|
||||
@@ -1,15 +0,0 @@
|
||||
- name: Binaries
|
||||
archive:
|
||||
- build_debug/memgraph
|
||||
- build_release/memgraph
|
||||
- build_release/tools/src/mg_client
|
||||
- build_release/tools/src/mg_import_csv
|
||||
- config
|
||||
filename: binaries.tar.gz
|
||||
|
||||
- name: Doxygen documentation
|
||||
cd: docs/doxygen/html
|
||||
archive:
|
||||
- .
|
||||
filename: documentation.tar.gz
|
||||
host: true
|
||||
@@ -1,94 +0,0 @@
|
||||
- name: Diff build
|
||||
project: ^mg-master-diff$
|
||||
commands: |
|
||||
# Activate toolchain
|
||||
export PATH=/opt/toolchain-v1/bin:$PATH
|
||||
export LD_LIBRARY_PATH=/opt/toolchain-v1/lib:/opt/toolchain-v1/lib64
|
||||
|
||||
# Copy untouched repository to parent folder.
|
||||
cd ..
|
||||
cp -r memgraph parent
|
||||
cd memgraph
|
||||
|
||||
# Initialize and create documentation.
|
||||
TIMEOUT=1200 ./init
|
||||
doxygen Doxyfile
|
||||
|
||||
# Remove default build directory.
|
||||
rm -r build
|
||||
|
||||
# Build debug binaries.
|
||||
mkdir build_debug
|
||||
cd build_debug
|
||||
cmake ..
|
||||
TIMEOUT=1200 make -j$THREADS
|
||||
|
||||
# Build coverage binaries.
|
||||
cd ..
|
||||
# TODO: uncomment this build once single node and ha are split
|
||||
# mkdir build_coverage
|
||||
# cd build_coverage
|
||||
# cmake -DTEST_COVERAGE=ON ..
|
||||
# TIMEOUT=1200 make -j$THREADS memgraph__unit
|
||||
ln -s build_debug build_coverage
|
||||
|
||||
# Build release binaries.
|
||||
# cd ..
|
||||
mkdir build_release
|
||||
cd build_release
|
||||
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||
TIMEOUT=1200 make -j$THREADS
|
||||
cd ..
|
||||
|
||||
# Checkout to parent commit and initialize.
|
||||
cd ../parent
|
||||
git checkout HEAD~1
|
||||
TIMEOUT=1200 ./init
|
||||
|
||||
# Build parent release binaries.
|
||||
mkdir build_release
|
||||
cd build_release
|
||||
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||
TIMEOUT=1200 make -j$THREADS memgraph memgraph__macro_benchmark
|
||||
|
||||
|
||||
# release build is the default one
|
||||
- name: Release build
|
||||
commands: |
|
||||
# Activate toolchain
|
||||
export PATH=/opt/toolchain-v1/bin:$PATH
|
||||
export LD_LIBRARY_PATH=/opt/toolchain-v1/lib:/opt/toolchain-v1/lib64
|
||||
|
||||
# Initialize and create documentation.
|
||||
TIMEOUT=1200 ./init
|
||||
doxygen Doxyfile
|
||||
|
||||
# Remove default build directory.
|
||||
rm -r build
|
||||
|
||||
# Build debug binaries.
|
||||
mkdir build_debug
|
||||
cd build_debug
|
||||
cmake ..
|
||||
TIMEOUT=1200 make -j$THREADS
|
||||
|
||||
# Build coverage binaries.
|
||||
cd ..
|
||||
# TODO: uncomment this build once single node and ha are split
|
||||
# mkdir build_coverage
|
||||
# cd build_coverage
|
||||
# cmake -DTEST_COVERAGE=ON ..
|
||||
# TIMEOUT=1200 make -j$THREADS memgraph__unit
|
||||
ln -s build_debug build_coverage
|
||||
|
||||
# Build release binaries.
|
||||
# cd ..
|
||||
mkdir build_release
|
||||
cd build_release
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DUSE_READLINE=OFF ..
|
||||
TIMEOUT=1200 make -j$THREADS
|
||||
|
||||
# Create Debian package.
|
||||
mkdir output
|
||||
cd output
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
@@ -39,14 +39,6 @@ modifications:
|
||||
value: "/var/log/memgraph/memgraph.log"
|
||||
override: true
|
||||
|
||||
- name: "bolt_cert_file"
|
||||
value: "/etc/memgraph/ssl/cert.pem"
|
||||
override: true
|
||||
|
||||
- name: "bolt_key_file"
|
||||
value: "/etc/memgraph/ssl/key.pem"
|
||||
override: true
|
||||
|
||||
- name: "bolt_num_workers"
|
||||
value: ""
|
||||
override: false
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
project(mg_customers)
|
||||
add_subdirectory(otto)
|
||||
@@ -1,70 +0,0 @@
|
||||
DISCLAIMER: this is just an initial test, graph might not resemble
|
||||
the graph in the use case at all and the data might be completely
|
||||
irrelevant.
|
||||
|
||||
We tried generating a few sample graphs from the vague description
|
||||
given in the use case doc. Then we tried writing queries that would
|
||||
solve the problem of updating nodes when a leaf value changes,
|
||||
assuming all the internal nodes compute only the sum function.
|
||||
|
||||
We start by creating an index on `id` property to improve initial lookup
|
||||
performance:
|
||||
|
||||
CREATE INDEX ON :Leaf(id)
|
||||
|
||||
Set values of all leafs to 1:
|
||||
|
||||
MATCH (u:Leaf) SET u.value = 1
|
||||
|
||||
Now we initialize the values of all other nodes in the graph:
|
||||
|
||||
MATCH (u) WHERE NOT u:Leaf SET u.value = 0
|
||||
|
||||
MATCH (u) WITH u
|
||||
ORDER BY u.topological_index DESC
|
||||
MATCH (u)-->(v) SET u.value = u.value + v.value
|
||||
|
||||
Change the value of a leaf:
|
||||
|
||||
MATCH (u:Leaf {id: "18"}) SET u.value = 10
|
||||
|
||||
We have to reset all the updated nodes to a neutral element:
|
||||
|
||||
MATCH (u:Leaf {id: "18"})<-[* bfs]-(v)
|
||||
WHERE NOT v:Leaf SET v.value = 0
|
||||
|
||||
Finally, we recalculate their values in topological order:
|
||||
|
||||
MATCH (u:Leaf {id: "18"})<-[* bfs]-(v)
|
||||
WITH v ORDER BY v.topological_index DESC
|
||||
MATCH (v)-->(w) SET v.value = v.value + w.value
|
||||
|
||||
There are a few assumptions made worth pointing out.
|
||||
|
||||
* We are able to efficiently maintain topological order
|
||||
of vertices in the graph.
|
||||
|
||||
* It is possible to accumulate the value of the function. Formally:
|
||||
$$f(x_1, x_2, ..., x_n) = g(...(g(g(x_1, x_2), x_3), ...), x_n)$$
|
||||
|
||||
* There is a neutral element for the operation. However, this
|
||||
assumption can be dropped by introducing an artificial neutral element.
|
||||
|
||||
Number of operations required is proportional to the sum of degrees of affected
|
||||
nodes.
|
||||
|
||||
We generated graph with $10^5$ nodes ($20\ 000$ nodes in each layer), varied the
|
||||
degree distribution in node layers and measured time for the query to execute:
|
||||
|
||||
| # | Root-Category-Group degree | Group-CustomGroup-Leaf degree | Time |
|
||||
|:-:|:---------------------------:|:-----------------------------:|:---------:|
|
||||
| 1 | [1, 10] | [20, 40] | ~1.1s |
|
||||
| 2 | [1, 10] | [50, 100] | ~2.5s |
|
||||
| 3 | [10, 50] | [50, 100] | ~3.3s |
|
||||
|
||||
Due to the structure of the graph, update of a leaf required update of almost
|
||||
all the nodes in the graph so we don't show times required for initial graph
|
||||
update and update after leaf change separately.
|
||||
|
||||
However, there is not enough info on the use case to make the test more
|
||||
sophisticated.
|
||||
@@ -1,71 +0,0 @@
|
||||
---
|
||||
title: "Elliott Management"
|
||||
subtitle: "Proof of Concept Report"
|
||||
header-title: "Elliott Management POC"
|
||||
date: 2017-10-28
|
||||
copyright: "©2017 Memgraph Ltd. All rights reserved."
|
||||
titlepage: true
|
||||
titlepage-color: FFFFFF
|
||||
titlepage-text-color: 101010
|
||||
titlepage-rule-color: 101010
|
||||
titlepage-rule-height: 1
|
||||
...
|
||||
|
||||
# Introduction
|
||||
|
||||
We tried generating a few sample graphs from the description given at
|
||||
the in-person meetings. Then, we tried writing queries that would solve
|
||||
the problem of updating nodes when a leaf value changes, assuming all the
|
||||
internal nodes compute only the sum function.
|
||||
|
||||
# Technical details
|
||||
|
||||
We started by creating an index on `id` property to improve initial lookup
|
||||
performance:
|
||||
|
||||
CREATE INDEX ON :Leaf(id)
|
||||
|
||||
Afther that, we set values of all leafs to 1:
|
||||
|
||||
MATCH (u:Leaf) SET u.value = 1
|
||||
|
||||
We then initialized the values of all other nodes in the graph:
|
||||
|
||||
MATCH (u) WHERE NOT u:Leaf SET u.value = 0
|
||||
|
||||
MATCH (u) WITH u
|
||||
ORDER BY u.topological_index DESC
|
||||
MATCH (u)-->(v) SET u.value = u.value + v.value
|
||||
|
||||
Leaf value change and update of affected values in the graph can
|
||||
be done using three queries. To change the value of a leaf:
|
||||
|
||||
MATCH (u:Leaf {id: "18"}) SET u.value = 10
|
||||
|
||||
Then we had to reset all the affected nodes to the neutral element:
|
||||
|
||||
MATCH (u:Leaf {id: "18"})<-[* bfs]-(v)
|
||||
WHERE NOT v:Leaf SET v.value = 0
|
||||
|
||||
Finally, we recalculated their values in topological order:
|
||||
|
||||
MATCH (u:Leaf {id: "18"})<-[* bfs]-(v)
|
||||
WITH v ORDER BY v.topological_index DESC
|
||||
MATCH (v)-->(w) SET v.value = v.value + w.value
|
||||
|
||||
There are a few assumptions necessary for the approach above to work.
|
||||
|
||||
* We are able to maintain topological order of vertices during graph
|
||||
structure changes.
|
||||
|
||||
* It is possible to accumulate the value of the function. Formally:
|
||||
$$f(x_1, x_2, ..., x_n) = g(...(g(g(x_1, x_2), x_3), ...), x_n)$$
|
||||
|
||||
* There is a neutral element for the operation. However, this
|
||||
assumption can be dropped by introducing an artificial neutral element.
|
||||
|
||||
Above assumptions could be changed, relaxed or dropped, depending on the
|
||||
specifics of the use case.
|
||||
|
||||
Number of operations required is proportional to the sum of degrees of affected
|
||||
nodes.
|
||||
@@ -1,12 +0,0 @@
|
||||
CREATE INDEX ON :Leaf(id);
|
||||
MATCH (u:Leaf) SET u.value = 1;
|
||||
MATCH (u) WHERE NOT u:Leaf SET u.value = 0;
|
||||
MATCH (u) WITH u
|
||||
ORDER BY u.topological_index DESC
|
||||
MATCH (u)-->(v) SET u.value = u.value + v.value;
|
||||
MATCH (u:Leaf {id: "85000"}) SET u.value = 10;
|
||||
MATCH (u:Leaf {id: "85000"})<-[* bfs]-(v)
|
||||
WHERE NOT v:Leaf SET v.value = 0;
|
||||
MATCH (u:Leaf {id: "85000"})<-[* bfs]-(v)
|
||||
WITH v ORDER BY v.topological_index DESC
|
||||
MATCH (v)-->(w) SET v.value = v.value + w.value;
|
||||
@@ -1,125 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Generates a DAG from JSON spec in [config] and outputs nodes to
|
||||
[filename]_nodes, and edges to [filename]_edges in format convertible
|
||||
to Memgraph snapshot.
|
||||
|
||||
Here's an example JSON spec:
|
||||
|
||||
{
|
||||
"layers": [
|
||||
{
|
||||
"name": "A",
|
||||
"sublayers": 1,
|
||||
"degree_lo": 1,
|
||||
"degree_hi": 3,
|
||||
"nodes": 4
|
||||
},
|
||||
{
|
||||
"name": "B",
|
||||
"sublayers": 3,
|
||||
"degree_lo": 2,
|
||||
"degree_hi": 3,
|
||||
"nodes": 10
|
||||
},
|
||||
{
|
||||
"name": "C",
|
||||
"sublayers": 1,
|
||||
"degree_lo": 1,
|
||||
"degree_hi": 1,
|
||||
"nodes": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Nodes from each layer will be randomly divided into sublayers. A node can
|
||||
only have edges pointing to nodes in lower sublayers of the same layer, or
|
||||
to nodes from the layer directly below it. Out-degree is chosen uniformly
|
||||
random from [degree_lo, degree_hi] interval."""
|
||||
|
||||
import argparse
|
||||
from itertools import accumulate
|
||||
import json
|
||||
import random
|
||||
|
||||
|
||||
def _split_into_sum(n, k):
|
||||
assert 1 <= n, "n should be at least 1"
|
||||
assert k <= n, "k shouldn't be greater than n"
|
||||
xs = [0] + sorted(random.sample(range(1, n), k-1)) + [n]
|
||||
return [b - a for a, b in zip(xs, xs[1:])]
|
||||
|
||||
|
||||
def generate_dag(graph_config, seed=None):
|
||||
random.seed(seed)
|
||||
|
||||
nodes = []
|
||||
edges = []
|
||||
|
||||
layer_lo = 1
|
||||
for layer in graph_config:
|
||||
sublayers = _split_into_sum(layer['nodes'], layer['sublayers'])
|
||||
sub_range = accumulate([layer_lo] + sublayers)
|
||||
layer['sublayer_range'] = list(sub_range)
|
||||
nodes.extend([
|
||||
(u, layer['name'])
|
||||
for u in range(layer_lo, layer_lo + layer['nodes'])
|
||||
])
|
||||
layer_lo += layer['nodes']
|
||||
|
||||
edges = []
|
||||
|
||||
for layer, next_layer in zip(graph_config, graph_config[1:]):
|
||||
degree_lo = layer['degree_lo']
|
||||
degree_hi = layer['degree_hi']
|
||||
|
||||
sub_range = layer['sublayer_range']
|
||||
sub_range_next = next_layer['sublayer_range']
|
||||
|
||||
layer_lo = sub_range[0]
|
||||
next_layer_hi = sub_range_next[-1]
|
||||
|
||||
for sub_lo, sub_hi in zip(sub_range, sub_range[1:]):
|
||||
for u in range(sub_lo, sub_hi):
|
||||
num_edges = random.randint(degree_lo, degree_hi)
|
||||
for _ in range(num_edges):
|
||||
v = random.randint(sub_hi, next_layer_hi - 1)
|
||||
edges.append((u, v))
|
||||
|
||||
for sub_lo, sub_hi in zip(sub_range_next, sub_range_next[1:]):
|
||||
for u in range(sub_lo, sub_hi):
|
||||
v = random.randint(layer_lo, sub_lo - 1)
|
||||
edges.append((v, u))
|
||||
|
||||
return nodes, edges
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
description=__doc__)
|
||||
parser.add_argument('config', type=str, help='graph config JSON file')
|
||||
parser.add_argument('filename', type=str,
|
||||
help='nodes will be stored to filename_nodes, '
|
||||
'edges to filename_edges')
|
||||
parser.add_argument('--seed', type=int,
|
||||
help='seed for the random generator (default = '
|
||||
'current system time)')
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.config, 'r') as f:
|
||||
graph_config = json.loads(f.read())['layers']
|
||||
|
||||
nodes, edges = generate_dag(graph_config, seed=args.seed)
|
||||
|
||||
# print nodes into CSV file
|
||||
with open('{}_nodes'.format(args.filename), 'w') as out:
|
||||
out.write('nodeId:ID(Node),name,topological_index:Int,:LABEL\n')
|
||||
for node_id, layer in nodes:
|
||||
out.write('{0},{1}{0},{0},{1}\n'.format(node_id, layer))
|
||||
|
||||
# print edges into CSV file
|
||||
with open('{}_edges'.format(args.filename), 'w') as out:
|
||||
out.write(':START_ID(Node),:END_ID(Node),:TYPE\n')
|
||||
for u, v in edges:
|
||||
out.write('{},{},child\n'.format(u, v))
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"layers": [
|
||||
{
|
||||
"name": "Root",
|
||||
"sublayers": 1,
|
||||
"degree_lo": 1,
|
||||
"degree_hi": 10,
|
||||
"nodes": 20000
|
||||
},
|
||||
{
|
||||
"name": "Category",
|
||||
"sublayers": 5,
|
||||
"degree_lo": 1,
|
||||
"degree_hi": 10,
|
||||
"nodes": 20000
|
||||
},
|
||||
{
|
||||
"name": "Group",
|
||||
"sublayers": 1,
|
||||
"degree_lo": 20,
|
||||
"degree_hi": 40,
|
||||
"nodes": 20000
|
||||
},
|
||||
{
|
||||
"name": "CustomGroup",
|
||||
"sublayers": 15,
|
||||
"degree_lo": 20,
|
||||
"degree_hi": 40,
|
||||
"nodes": 20000
|
||||
},
|
||||
{
|
||||
"name": "Leaf",
|
||||
"sublayers": 1,
|
||||
"degree_lo": 1,
|
||||
"degree_hi": 1,
|
||||
"nodes": 20000
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"layers": [
|
||||
{
|
||||
"name": "Root",
|
||||
"sublayers": 1,
|
||||
"degree_lo": 1,
|
||||
"degree_hi": 10,
|
||||
"nodes": 20000
|
||||
},
|
||||
{
|
||||
"name": "Category",
|
||||
"sublayers": 5,
|
||||
"degree_lo": 1,
|
||||
"degree_hi": 10,
|
||||
"nodes": 20000
|
||||
},
|
||||
{
|
||||
"name": "Group",
|
||||
"sublayers": 1,
|
||||
"degree_lo": 50,
|
||||
"degree_hi": 100,
|
||||
"nodes": 20000
|
||||
},
|
||||
{
|
||||
"name": "CustomGroup",
|
||||
"sublayers": 15,
|
||||
"degree_lo": 50,
|
||||
"degree_hi": 100,
|
||||
"nodes": 20000
|
||||
},
|
||||
{
|
||||
"name": "Leaf",
|
||||
"sublayers": 1,
|
||||
"degree_lo": 50,
|
||||
"degree_hi": 100,
|
||||
"nodes": 20000
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"layers": [
|
||||
{
|
||||
"name": "Root",
|
||||
"sublayers": 1,
|
||||
"degree_lo": 10,
|
||||
"degree_hi": 50,
|
||||
"nodes": 20000
|
||||
},
|
||||
{
|
||||
"name": "Category",
|
||||
"sublayers": 5,
|
||||
"degree_lo": 10,
|
||||
"degree_hi": 50,
|
||||
"nodes": 20000
|
||||
},
|
||||
{
|
||||
"name": "Group",
|
||||
"sublayers": 1,
|
||||
"degree_lo": 50,
|
||||
"degree_hi": 100,
|
||||
"nodes": 20000
|
||||
},
|
||||
{
|
||||
"name": "CustomGroup",
|
||||
"sublayers": 15,
|
||||
"degree_lo": 50,
|
||||
"degree_hi": 100,
|
||||
"nodes": 20000
|
||||
},
|
||||
{
|
||||
"name": "Leaf",
|
||||
"sublayers": 1,
|
||||
"degree_lo": 50,
|
||||
"degree_hi": 100,
|
||||
"nodes": 20000
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
set(exec_name customers_otto_parallel_connected_components)
|
||||
add_executable(${exec_name} parallel_connected_components.cpp)
|
||||
target_link_libraries(${exec_name} memgraph_lib)
|
||||
@@ -1,118 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
This script attempts to evaluate the feasibility of using Memgraph for
|
||||
Otto group's usecase. The usecase is finding connected componentes in
|
||||
a large, very sparse graph (cca 220M nodes, 250M edges), based on a dynamic
|
||||
inclusion / exclusion of edges (w.r.t variable parameters and the source node
|
||||
type).
|
||||
|
||||
This implementation defines a random graph with the given number of nodes
|
||||
and edges and looks for connected components using breadth-first expansion.
|
||||
Edges are included / excluded based on a simple expression, only demonstrating
|
||||
possible usage.
|
||||
"""
|
||||
|
||||
from argparse import ArgumentParser
|
||||
import logging
|
||||
from time import time
|
||||
from collections import defaultdict
|
||||
from math import log2
|
||||
from random import randint
|
||||
|
||||
from neo4j.v1 import GraphDatabase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def generate_graph(sess, node_count, edge_count):
|
||||
# An index that will speed-up edge creation.
|
||||
sess.run("CREATE INDEX ON :Node(id)").consume()
|
||||
|
||||
# Create the given number of nodes with a randomly selected type from:
|
||||
# [0.5, 1.5, 2.5].
|
||||
sess.run(("UNWIND range(0, {} - 1) AS id CREATE "
|
||||
"(:Node {{id: id, type: 0.5 + tointeger(rand() * 3)}})").format(
|
||||
node_count)).consume()
|
||||
|
||||
# Create the given number of edges, each with a 'value' property of
|
||||
# a random [0, 3.0) float. Each edge connects two random nodes, so the
|
||||
# expected node degree is (edge_count * 2 / node_count). Generate edges
|
||||
# so the connectivity is non-uniform (to produce connected components of
|
||||
# various sizes).
|
||||
sess.run(("UNWIND range(0, {0} - 1) AS id WITH id "
|
||||
"MATCH (from:Node {{id: tointeger(rand() * {1})}}), "
|
||||
"(to:Node {{id: tointeger(rand() * {1} * id / {0})}}) "
|
||||
"CREATE (from)-[:Edge {{value: 3 * rand()}}]->(to)").format(
|
||||
edge_count, node_count)).consume()
|
||||
|
||||
|
||||
def get_connected_ids(sess, node_id):
|
||||
# Matches a node with the given ID and returns the IDs of all the nodes
|
||||
# it is connected to. Note that within the BFS lambda expression there
|
||||
# is an expression used to filter out edges expanded over.
|
||||
return sess.run((
|
||||
"MATCH (from:Node {{id: {}}})-"
|
||||
"[*bfs (e, n | abs(from.type - e.value) < 0.80)]-(d) "
|
||||
"RETURN count(*) AS c").format(node_id)).data()[0]['c']
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--endpoint', type=str, default='localhost:7687',
|
||||
help='Memgraph instance endpoint. ')
|
||||
parser.add_argument('--node-count', type=int, default=1000,
|
||||
help='The number of nodes in the graph')
|
||||
parser.add_argument('--edge-count', type=int, default=1000,
|
||||
help='The number of edges in the graph')
|
||||
parser.add_argument('--sample-count', type=int, default=None,
|
||||
help='The number of samples to take')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
log.info("Memgraph - Otto test database generator")
|
||||
logging.getLogger("neo4j").setLevel(logging.WARNING)
|
||||
|
||||
driver = GraphDatabase.driver(
|
||||
'bolt://' + args.endpoint,
|
||||
auth=("ignored", "ignored"),
|
||||
encrypted=False)
|
||||
sess = driver.session()
|
||||
|
||||
sess.run("MATCH (n) DETACH DELETE n").consume()
|
||||
|
||||
log.info("Generating graph with %s nodes and %s edges...",
|
||||
args.node_count, args.edge_count)
|
||||
generate_graph(sess, args.node_count, args.edge_count)
|
||||
|
||||
# Track which vertices have been found as part of a component.
|
||||
start_time = time()
|
||||
max_query_time = 0
|
||||
log.info("Looking for connected components...")
|
||||
# Histogram of log2 sizes of connected components found.
|
||||
histogram = defaultdict(int)
|
||||
sample_count = args.sample_count if args.sample_count else args.node_count
|
||||
for i in range(sample_count):
|
||||
node_id = randint(0, args.node_count - 1)
|
||||
query_start_time = time()
|
||||
log2_size = int(log2(1 + get_connected_ids(sess, node_id)))
|
||||
max_query_time = max(max_query_time, time() - query_start_time)
|
||||
histogram[log2_size] += 1
|
||||
elapsed = time() - start_time
|
||||
log.info("Connected components found in %.2f sec (avg %.2fms, max %.2fms)",
|
||||
elapsed, elapsed / sample_count * 1000, max_query_time * 1000)
|
||||
log.info("Component size histogram (count | range)")
|
||||
for log2_size, count in sorted(histogram.items()):
|
||||
log.info("\t%5d | %d - %d", count, 2 ** log2_size,
|
||||
2 ** (log2_size + 1) - 1)
|
||||
|
||||
sess.close()
|
||||
driver.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,207 +0,0 @@
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <stack>
|
||||
#include <thread>
|
||||
|
||||
#include "gflags/gflags.h"
|
||||
#include "glog/logging.h"
|
||||
|
||||
#include "data_structures/union_find.hpp"
|
||||
#include "database/graph_db.hpp"
|
||||
#include "database/graph_db_accessor.hpp"
|
||||
#include "storage/property_value.hpp"
|
||||
#include "threading/sync/spinlock.hpp"
|
||||
#include "utils/bound.hpp"
|
||||
#include "utils/timer.hpp"
|
||||
|
||||
DEFINE_int32(thread_count, 1, "Number of threads");
|
||||
DEFINE_int32(vertex_count, 1000, "Number of vertices");
|
||||
DEFINE_int32(edge_count, 1000, "Number of edges");
|
||||
DECLARE_int32(gc_cycle_sec);
|
||||
|
||||
static const std::string kLabel{"kLabel"};
|
||||
static const std::string kProperty{"kProperty"};
|
||||
|
||||
void GenerateGraph(database::GraphDb &db) {
|
||||
{
|
||||
database::GraphDbAccessor dba{db};
|
||||
dba.BuildIndex(dba.Label(kLabel), dba.Property(kProperty));
|
||||
dba.Commit();
|
||||
}
|
||||
|
||||
// Randomize the sequence of IDs of created vertices and edges to simulate
|
||||
// real-world lack of locality.
|
||||
auto make_id_vector = [](size_t size) {
|
||||
gid::Generator generator{0};
|
||||
std::vector<gid::Gid> ids(size);
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
ids[i] = generator.Next(std::experimental::nullopt);
|
||||
std::random_shuffle(ids.begin(), ids.end());
|
||||
return ids;
|
||||
};
|
||||
|
||||
std::vector<VertexAccessor> vertices;
|
||||
vertices.reserve(FLAGS_vertex_count);
|
||||
{
|
||||
CHECK(FLAGS_vertex_count % FLAGS_thread_count == 0)
|
||||
<< "Thread count must be a factor of vertex count";
|
||||
LOG(INFO) << "Generating " << FLAGS_vertex_count << " vertices...";
|
||||
utils::Timer timer;
|
||||
auto vertex_ids = make_id_vector(FLAGS_vertex_count);
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
SpinLock vertices_lock;
|
||||
for (int i = 0; i < FLAGS_thread_count; ++i) {
|
||||
threads.emplace_back([&db, &vertex_ids, &vertices, &vertices_lock, i]() {
|
||||
database::GraphDbAccessor dba{db};
|
||||
auto label = dba.Label(kLabel);
|
||||
auto property = dba.Property(kProperty);
|
||||
auto batch_size = FLAGS_vertex_count / FLAGS_thread_count;
|
||||
for (int j = i * batch_size; j < (i + 1) * batch_size; ++j) {
|
||||
auto vertex = dba.InsertVertex(vertex_ids[j]);
|
||||
vertex.add_label(label);
|
||||
vertex.PropsSet(property, static_cast<int64_t>(vertex_ids[j]));
|
||||
vertices_lock.lock();
|
||||
vertices.emplace_back(vertex);
|
||||
vertices_lock.unlock();
|
||||
}
|
||||
dba.Commit();
|
||||
});
|
||||
}
|
||||
for (auto &t : threads) t.join();
|
||||
LOG(INFO) << "Generated " << FLAGS_vertex_count << " vertices in "
|
||||
<< timer.Elapsed().count() << " seconds.";
|
||||
}
|
||||
{
|
||||
database::GraphDbAccessor dba{db};
|
||||
for (int i = 0; i < FLAGS_vertex_count; ++i)
|
||||
vertices[i] = *dba.Transfer(vertices[i]);
|
||||
|
||||
LOG(INFO) << "Generating " << FLAGS_edge_count << " edges...";
|
||||
auto edge_ids = make_id_vector(FLAGS_edge_count);
|
||||
std::mt19937 pseudo_rand_gen{std::random_device{}()};
|
||||
std::uniform_int_distribution<> rand_dist{0, FLAGS_vertex_count - 1};
|
||||
auto edge_type = dba.EdgeType("edge");
|
||||
utils::Timer timer;
|
||||
for (int i = 0; i < FLAGS_edge_count; ++i)
|
||||
dba.InsertEdge(vertices[rand_dist(pseudo_rand_gen)],
|
||||
vertices[rand_dist(pseudo_rand_gen)], edge_type,
|
||||
edge_ids[i]);
|
||||
dba.Commit();
|
||||
LOG(INFO) << "Generated " << FLAGS_edge_count << " edges in "
|
||||
<< timer.Elapsed().count() << " seconds.";
|
||||
}
|
||||
}
|
||||
|
||||
auto EdgeIteration(database::GraphDb &db) {
|
||||
database::GraphDbAccessor dba{db};
|
||||
int64_t sum{0};
|
||||
for (auto edge : dba.Edges(false)) sum += edge.from().gid() + edge.to().gid();
|
||||
return sum;
|
||||
}
|
||||
|
||||
auto VertexIteration(database::GraphDb &db) {
|
||||
database::GraphDbAccessor dba{db};
|
||||
int64_t sum{0};
|
||||
for (auto v : dba.Vertices(false))
|
||||
for (auto e : v.out()) sum += e.gid() + e.to().gid();
|
||||
return sum;
|
||||
}
|
||||
|
||||
auto ConnectedComponentsEdges(database::GraphDb &db) {
|
||||
UnionFind<int64_t> connectivity{FLAGS_vertex_count};
|
||||
database::GraphDbAccessor dba{db};
|
||||
for (auto edge : dba.Edges(false))
|
||||
connectivity.Connect(edge.from().gid(), edge.to().gid());
|
||||
return connectivity.Size();
|
||||
}
|
||||
|
||||
auto ConnectedComponentsVertices(database::GraphDb &db) {
|
||||
UnionFind<int64_t> connectivity{FLAGS_vertex_count};
|
||||
database::GraphDbAccessor dba{db};
|
||||
for (auto from : dba.Vertices(false)) {
|
||||
for (auto out_edge : from.out())
|
||||
connectivity.Connect(from.gid(), out_edge.to().gid());
|
||||
}
|
||||
return connectivity.Size();
|
||||
}
|
||||
|
||||
auto ConnectedComponentsVerticesParallel(database::GraphDb &db) {
|
||||
UnionFind<int64_t> connectivity{FLAGS_vertex_count};
|
||||
SpinLock connectivity_lock;
|
||||
|
||||
// Define bounds of vertex IDs for each thread to use.
|
||||
std::vector<PropertyValue> bounds;
|
||||
for (int64_t i = 0; i < FLAGS_thread_count; ++i)
|
||||
bounds.emplace_back(i * FLAGS_vertex_count / FLAGS_thread_count);
|
||||
bounds.emplace_back(std::numeric_limits<int64_t>::max());
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
for (int i = 0; i < FLAGS_thread_count; ++i) {
|
||||
threads.emplace_back(
|
||||
[&connectivity, &connectivity_lock, &bounds, &db, i]() {
|
||||
database::GraphDbAccessor dba{db};
|
||||
for (auto from :
|
||||
dba.Vertices(dba.Label(kLabel), dba.Property(kProperty),
|
||||
utils::MakeBoundInclusive(bounds[i]),
|
||||
utils::MakeBoundExclusive(bounds[i + 1]), false)) {
|
||||
for (auto out_edge : from.out()) {
|
||||
std::lock_guard<SpinLock> lock{connectivity_lock};
|
||||
connectivity.Connect(from.gid(), out_edge.to().gid());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
for (auto &t : threads) t.join();
|
||||
return connectivity.Size();
|
||||
}
|
||||
|
||||
auto Expansion(database::GraphDb &db) {
|
||||
std::vector<int> component_ids(FLAGS_vertex_count, -1);
|
||||
int next_component_id{0};
|
||||
std::stack<VertexAccessor> expansion_stack;
|
||||
database::GraphDbAccessor dba{db};
|
||||
for (auto v : dba.Vertices(false)) {
|
||||
if (component_ids[v.gid()] != -1) continue;
|
||||
auto component_id = next_component_id++;
|
||||
expansion_stack.push(v);
|
||||
while (!expansion_stack.empty()) {
|
||||
auto next_v = expansion_stack.top();
|
||||
expansion_stack.pop();
|
||||
if (component_ids[next_v.gid()] != -1) continue;
|
||||
component_ids[next_v.gid()] = component_id;
|
||||
for (auto e : next_v.out()) expansion_stack.push(e.to());
|
||||
for (auto e : next_v.in()) expansion_stack.push(e.from());
|
||||
}
|
||||
}
|
||||
|
||||
return next_component_id;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
google::InitGoogleLogging(argv[0]);
|
||||
FLAGS_gc_cycle_sec = -1;
|
||||
|
||||
database::SingleNode db;
|
||||
GenerateGraph(db);
|
||||
auto timed_call = [&db](auto callable, const std::string &descr) {
|
||||
LOG(INFO) << "Running " << descr << "...";
|
||||
utils::Timer timer;
|
||||
auto result = callable(db);
|
||||
LOG(INFO) << "\tDone in " << timer.Elapsed().count()
|
||||
<< " seconds, result: " << result;
|
||||
};
|
||||
timed_call(EdgeIteration, "Edge iteration");
|
||||
timed_call(VertexIteration, "Vertex iteration");
|
||||
timed_call(ConnectedComponentsEdges, "Connected components - Edges");
|
||||
timed_call(ConnectedComponentsVertices, "Connected components - Vertices");
|
||||
timed_call(ConnectedComponentsVerticesParallel,
|
||||
"Parallel connected components - Vertices");
|
||||
timed_call(Expansion, "Expansion");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
WITH tointeger(rand() * 40000000) AS from_id
|
||||
MATCH (from:Node {id : from_id}) WITH from
|
||||
MATCH path = (from)-[*bfs..50 (e, n | degree(n) < 50)]->(to) WITH path LIMIT 10000 WHERE to.fraudulent
|
||||
RETURN path, size(path)
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"indexes":[
|
||||
"Node.id"
|
||||
],
|
||||
"nodes":[
|
||||
{
|
||||
"count":40000000,
|
||||
"labels":[
|
||||
"Node"
|
||||
],
|
||||
"properties":{
|
||||
"id":{
|
||||
"type":"counter",
|
||||
"param":"Node.id"
|
||||
},
|
||||
"fraudulent":{
|
||||
"type":"bernoulli",
|
||||
"param":0.0005
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges":[
|
||||
{
|
||||
"count":80000000,
|
||||
"from":"Node",
|
||||
"to":"Node",
|
||||
"type":"Edge"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"indexes" : ["Card.id", "Pos.id", "Transaction.fraud_reported"],
|
||||
"nodes" : [
|
||||
{
|
||||
"count_per_worker" : 1250000,
|
||||
"label" : "Card"
|
||||
},
|
||||
{
|
||||
"count_per_worker" : 1250000,
|
||||
"label" : "Pos"
|
||||
},
|
||||
{
|
||||
"count_per_worker" : 2500000,
|
||||
"label" : "Transaction"
|
||||
}
|
||||
],
|
||||
"compromised_pos_probability" : 0.2,
|
||||
"fraud_reported_probability" : 0.1,
|
||||
"hop_probability" : 0.1
|
||||
}
|
||||
5
docs/dev/communication/contents.md
Normal file
5
docs/dev/communication/contents.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Communication
|
||||
|
||||
## Bolt
|
||||
|
||||
Memgraph implements [Bolt communication protocol](https://7687.org/).
|
||||
@@ -29,6 +29,33 @@ You should use a pointer to `const` then. The primary reason being is that
|
||||
references obscure the semantics of moving an object, thus making bugs with
|
||||
references pointing to invalid memory harder to track down.
|
||||
|
||||
Example of this can be seen while capturing member variables by reference
|
||||
inside a lambda.
|
||||
Let's define a class that has two members, where one of those members is a
|
||||
lambda that captures the other member by reference.
|
||||
|
||||
```cpp
|
||||
struct S {
|
||||
std::function<void()> foo;
|
||||
int bar;
|
||||
|
||||
S() : foo([&]() { std::cout << bar; })
|
||||
{}
|
||||
};
|
||||
```
|
||||
What would happend if we move an instance of this object? Our lambda
|
||||
reference capture will point to the same location as before, i.e. it
|
||||
will point to the **old** memory location of `bar`. This means we have
|
||||
a dangling reference in our code!
|
||||
There are multiple ways to avoid this. The simple solutions would be
|
||||
capturing by value or disabling move constructors/assignments.
|
||||
Still, if we capture by reference an object that is not a member
|
||||
of the struct containing the lambda, we can still have a dangling
|
||||
reference if we move that object somewhere in our code and there is
|
||||
nothing we can do to prevent that.
|
||||
So, be careful with lambda catptures, and remember that references are
|
||||
still a pointer under the hood!
|
||||
|
||||
[Style guide reference](https://google.github.io/styleguide/cppguide.html#Reference_Arguments)
|
||||
|
||||
#### Constructors & RAII
|
||||
@@ -47,6 +74,66 @@ apply to destructors, which are not allowed to even throw exceptions.
|
||||
|
||||
[Style guide reference](https://google.github.io/styleguide/cppguide.html#Doing_Work_in_Constructors)
|
||||
|
||||
#### Constructors and member variables
|
||||
|
||||
One of the most powerful tools in C++ is the move semantics. We won't go into
|
||||
detail how it works, but you should know how to utilize it as much as
|
||||
possible. In our example we will define a small `struct` called `S` which
|
||||
contains only a single member, `text` of type `std::string`.
|
||||
```cpp
|
||||
struct S {
|
||||
std::string text;
|
||||
};
|
||||
```
|
||||
We want to define a constructor that takes a `std::string`, and saves its
|
||||
value in `text`. This is a common situation, where the constructor takes
|
||||
a value, and saves it in the object to be constructed.
|
||||
|
||||
Our first implementation would look like this:
|
||||
```cpp
|
||||
S(const std::string &s) : text(s) {}
|
||||
```
|
||||
|
||||
This is a valid solution but with one downside - we always copy. If we
|
||||
construct an object like this:
|
||||
```cpp
|
||||
S s("some text");
|
||||
```
|
||||
we would create a temporary `std::string` object and then copy it to our member
|
||||
variable.
|
||||
|
||||
Of course, we know what to do now - we will capture temporary variables using
|
||||
`&&` and move it into our `text` variable.
|
||||
```cpp
|
||||
S(std::string &&s) : text(std::move(s)) {}
|
||||
```
|
||||
|
||||
Now let's add an extra member variable of type `std::vector<int>` called
|
||||
`words`. Our constructors accept 2 values now - `std::vector<int>` and
|
||||
`std::string`. Those arguments could be passed by value, by reference, or
|
||||
as rvalues. To cover all the cases we need to define a dedicated constructor
|
||||
for each case.
|
||||
Fortunately, there are two simpler options, the first one is writing a
|
||||
templated constructor:
|
||||
```cpp
|
||||
template<typename T1, typename T2>
|
||||
S(T1 &&s, T2 &&v) : text(std::forward<T1>(s), words(std::forward<T2>(v) {}
|
||||
```
|
||||
But don't forget to define `requires` clause so you don't accept any type. This
|
||||
solution is optimal but really hard to read AND write. The second solution is
|
||||
something you should ALWAYS prefer in these simple cases where we only store
|
||||
one of the arguments:
|
||||
```cpp
|
||||
S(std::string s, std::vector<int> v) : text(std::move(s)), words(std::move(v)) {}
|
||||
```
|
||||
This way we have an almost optimal solution. The only extra operation we have is
|
||||
the extra move when we send an `lvalue`. We would copy the value to the `s`, and
|
||||
then move it to the `text` variable. Before, we would copy directly to `text`.
|
||||
Also, you should ALWAYS write const-correct code, meaning `s` and `v` cannot be
|
||||
`const` as it's not correct here. Why is that? You CANNOT move a const object!
|
||||
It would just degrade to copying the object. I would say that this is a small
|
||||
price to pay for a much cleaner and more maintainable code.
|
||||
|
||||
### Additional Style Conventions
|
||||
|
||||
Old code may have broken Google C++ Style accidentally, but the new code
|
||||
|
||||
@@ -1,288 +0,0 @@
|
||||
// dot -Tpng dependencies.dot -o /path/to/output.png
|
||||
|
||||
// TODO (buda): Put PropertyValueStore to storage namespace
|
||||
|
||||
digraph {
|
||||
// At the beginning of each block there is a default style for that block
|
||||
label="Memgraph Dependencies Diagram"; fontname="Roboto Bold"; fontcolor=black;
|
||||
fontsize=26; labelloc=top; labeljust=right;
|
||||
compound=true; // If true, allow edges between clusters
|
||||
rankdir=TB; // Alternatives: LR
|
||||
node [shape=record fontname="Roboto", fontsize=12, fontcolor=white];
|
||||
edge [color="#B5AFB7"];
|
||||
|
||||
// -- Legend --
|
||||
// dir=both arrowtail=diamond arrowhead=vee -> group ownership
|
||||
// dir=both arrowtail=none, arrowhead=vee -> ownership; stack or uptr
|
||||
|
||||
subgraph cluster_tcp_end_client_communication {
|
||||
label="TCP End Client Communication"; fontsize=14;
|
||||
node [style=filled, color="#DD2222" fillcolor="#DD2222"];
|
||||
|
||||
// Owned elements
|
||||
"communication::Server";
|
||||
"io::network::Socket";
|
||||
|
||||
// Intracluster connections
|
||||
"communication::Server" -> "io::network::Socket"
|
||||
[label="socket_" dir=both arrowtail=none arrowhead=vee];
|
||||
}
|
||||
|
||||
subgraph cluster_bolt_server {
|
||||
label="Bolt Server"; fontsize=14;
|
||||
node [style=filled, color="#62A2CA" fillcolor="#62A2CA"];
|
||||
|
||||
// Owned elements
|
||||
"communication::bolt::SessionData";
|
||||
"communication::bolt::Session";
|
||||
"communication::bolt::Encoder";
|
||||
"communication::bolt::Decoder";
|
||||
|
||||
// Intracluster connections
|
||||
"communication::bolt::Session" -> "communication::bolt::Encoder"
|
||||
[label="encoder_", dir=both arrowtail=none, arrowhead=vee];
|
||||
"communication::bolt::Session" -> "communication::bolt::Decoder"
|
||||
[label="decoder_", dir=both arrowtail=none, arrowhead=vee];
|
||||
}
|
||||
|
||||
subgraph cluster_opencypher_engine {
|
||||
label="openCypher Engine"; fontsize=14;
|
||||
node [style=filled, color="#68BDF6" fillcolor="#68BDF6"];
|
||||
|
||||
// Owned Elements
|
||||
"query::Interpreter";
|
||||
"query::AstTreeStorage";
|
||||
"query::TypedValue"
|
||||
"query::Path";
|
||||
"query::Simbol";
|
||||
"query::Context";
|
||||
"query::ExpressionEvaluator";
|
||||
"query::Frame";
|
||||
"query::SymbolTable";
|
||||
"query::plan::LogicalOperator";
|
||||
"query::plan::Cursor";
|
||||
"query::plan::CostEstimator";
|
||||
|
||||
// Intracluster connections
|
||||
"query::Interpreter" -> "query::AstTreeStorage"
|
||||
[label="ast_cache" dir=both arrowtail=diamond arrowhead=vee];
|
||||
"query::TypedValue" -> "query::Path";
|
||||
"query::plan::Cursor" -> "query::Frame";
|
||||
"query::plan::Cursor" -> "query::Context";
|
||||
"query::plan::LogicalOperator" -> "query::Symbol";
|
||||
"query::plan::LogicalOperator" -> "query::SymbolTable";
|
||||
"query::plan::LogicalOperator" -> "query::plan::Cursor";
|
||||
}
|
||||
|
||||
|
||||
subgraph cluster_storage {
|
||||
label="Storage" fontsize=14;
|
||||
node [style=filled, color="#FB6E00" fillcolor="#FB6E00"];
|
||||
|
||||
// Owned Elements
|
||||
"database::GraphDb";
|
||||
"database::GraphDbAccessor";
|
||||
"storage::Record";
|
||||
"storage::Vertex";
|
||||
"storage::Edge";
|
||||
"storage::RecordAccessor";
|
||||
"storage::VertexAccessor";
|
||||
"storage::EdgeAccessor";
|
||||
"storage::Common";
|
||||
"storage::Label";
|
||||
"storage::EdgeType";
|
||||
"storage::Property";
|
||||
"storage::compression";
|
||||
"storage::SingleNodeConcurrentIdMapper";
|
||||
"storage::Location";
|
||||
"storage::StorageTypesLocation";
|
||||
"PropertyValueStore";
|
||||
"storage::RecordLock";
|
||||
"mvcc::Version";
|
||||
"mvcc::Record";
|
||||
"mvcc::VersionList";
|
||||
|
||||
// Intracluster connections
|
||||
"storage::VertexAccessor" -> "storage::RecordAccessor"
|
||||
[arrowhead=onormal];
|
||||
"storage::EdgeAccessor" -> "storage::RecordAccessor"
|
||||
[arrowhead=onormal];
|
||||
"storage::RecordAccessor" -> "database::GraphDbAccessor"
|
||||
[style=dashed arrowhead=vee];
|
||||
"storage::Vertex" -> "mvcc::Record"
|
||||
[arrowhead=onormal];
|
||||
"storage::Edge" -> "mvcc::Record"
|
||||
[arrowhead=onormal];
|
||||
"storage::Edge" -> "PropertyValueStore"
|
||||
[arrowhead=vee];
|
||||
"storage::Vertex" -> "PropertyValueStore"
|
||||
[arrowhead=vee];
|
||||
"storage::Edge" -> "mvcc::VersionList"
|
||||
[label="from,to" arrowhead=vee style=dashed];
|
||||
"storage::VertexAccessor" -> "storage::Vertex"
|
||||
[arrowhead=vee];
|
||||
"storage::EdgeAccessor" -> "storage::Edge"
|
||||
[arrowhead=vee];
|
||||
"storage::SingleNodeConcurrentIdMapper" -> "storage::StorageTypesLocation"
|
||||
[arrowhead=vee];
|
||||
"storage::StorageTypesLocation" -> "storage::Location"
|
||||
[arrowhead=vee];
|
||||
"storage::Storage" -> "storage::StorageTypesLocation"
|
||||
[arrowhead=vee];
|
||||
"storage::Property" -> "storage::Common"
|
||||
[arrowhead=onormal];
|
||||
"storage::Label" -> "storage::Common"
|
||||
[arrowhead=onormal];
|
||||
"storage::EdgeType" -> "storage::Common"
|
||||
[arrowhead=onormal];
|
||||
"storage::Property" -> "storage::Location"
|
||||
[arrowhead=vee];
|
||||
"PropertyValueStore" -> "storage::Property"
|
||||
[arrowhead=vee];
|
||||
"PropertyValueStore" -> "storage::Location"
|
||||
[arrowhead=vee];
|
||||
"database::GraphDbAccessor" -> "database::GraphDb"
|
||||
[arrowhead=vee];
|
||||
"database::GraphDbAccessor" -> "tx::TransactionId"
|
||||
[arrowhead=vee];
|
||||
"mvcc::VersionList" -> "storge::RecordLock"
|
||||
[label="lock" arrowhead=vee];
|
||||
"mvcc::VersionList" -> "mvcc::Record"
|
||||
[label="head" arrowhead=vee];
|
||||
"mvcc::Record" -> "mvcc::Version"
|
||||
[arrowhead=onormal];
|
||||
|
||||
// Explicit positioning
|
||||
{rank=same;
|
||||
"database::GraphDbAccessor";
|
||||
"storage::VertexAccessor";
|
||||
"storage::EdgeAccessor";}
|
||||
{rank=same;
|
||||
"storage::Common";
|
||||
"storage::compression";}
|
||||
}
|
||||
|
||||
subgraph cluster_properties_on_disk {
|
||||
label="Properties on Disk" fontsize=14;
|
||||
node [style=filled, color="#102647" fillcolor="#102647"];
|
||||
|
||||
// Owned Elements
|
||||
"storage::KVStore";
|
||||
"rocksdb";
|
||||
|
||||
// Intracluster connections
|
||||
"storage::KVStore" -> "rocksdb";
|
||||
}
|
||||
|
||||
subgraph cluster_distributed {
|
||||
label="Distributed" fontsize=14;
|
||||
node [style=filled, color="#FFC500" fillcolor="#FFC500"];
|
||||
|
||||
// Owned Elements
|
||||
"distributed::DataManager";
|
||||
"distributed::DataRpcClients";
|
||||
|
||||
// Intracluster connections
|
||||
"distributed::DataManager" -> "distributed::DataRpcClients"
|
||||
[arrowhead=vee];
|
||||
"storage::RecordAccessor" -> "distributed::DataManager"
|
||||
[style=dashed arrowhead=vee];
|
||||
}
|
||||
|
||||
subgraph cluster_dynamic_partitioning {
|
||||
label="Dynamic Partitioning" fontsize=14;
|
||||
node [style=filled, color="#720096" fillcolor="#720096"];
|
||||
|
||||
// Owned Elements
|
||||
"DynamicPartitioner";
|
||||
}
|
||||
|
||||
subgraph cluster_security {
|
||||
label="Security" fontsize=14;
|
||||
node [style=filled, color="#857F87" fillcolor="#857F87"];
|
||||
|
||||
// Owned Elements
|
||||
"Communication Encryption";
|
||||
"Data Encryption";
|
||||
"Access Control";
|
||||
"Audit Logging";
|
||||
}
|
||||
|
||||
subgraph cluster_web_dashboard {
|
||||
label="Dashaboard" fontsize=14;
|
||||
node [style=filled, color="#FF0092" fillcolor="#FF0092"];
|
||||
|
||||
// Owned Elements
|
||||
"Memgraph Ops / Memgraph Cockpit";
|
||||
}
|
||||
|
||||
subgraph cluster_rpc {
|
||||
label="RPC" fontsize=14;
|
||||
node [style=filled, color="#857F87" fillcolor="#857F87"];
|
||||
|
||||
// Owned Elements
|
||||
"communication::rpc::Server";
|
||||
"communication::rpc::Client";
|
||||
}
|
||||
|
||||
subgraph cluster_ingestion {
|
||||
label="Ingestion" fontsize=14;
|
||||
node [style=filled, color="#0B6D88" fillcolor="#0B6D88"];
|
||||
|
||||
// Owned Elements
|
||||
"Extract";
|
||||
"Transform";
|
||||
"Load";
|
||||
"Amazon S3";
|
||||
"Kafka";
|
||||
|
||||
// Intracluster connections
|
||||
"Extract" -> "Amazon S3";
|
||||
"Extract" -> "Kafka";
|
||||
|
||||
// Explicit positioning
|
||||
{rank=same;"Extract";"Transform";"Load";}
|
||||
}
|
||||
|
||||
// -- Intercluster connections --
|
||||
// cluster_tcp_end_client_communication -- cluster_bolt_server
|
||||
"communication::Server" -> "communication::bolt::SessionData" [color=black];
|
||||
"communication::Server" -> "communication::bolt::Session" [color=black];
|
||||
// cluster_bolt_server -> cluster_storage
|
||||
"communication::bolt::SessionData" -> "database::GraphDb" [color=red];
|
||||
"communication::bolt::Session" -> "database::GraphDbAccessor" [color=red];
|
||||
// cluster_bolt_server -> cluster_opencypher_engine
|
||||
"communication::bolt::SessionData" -> "query::Interpreter" [color=red];
|
||||
// cluster_opencypher_engine -- cluster_storage
|
||||
"query::Interpreter" -> "database::GraphDbAccessor" [color=black];
|
||||
"query::Interpreter" -> "storage::VertexAccessor" [color=black];
|
||||
"query::Interpreter" -> "storage::EdgeAccessor" [color=black];
|
||||
"query::TypedValue" -> "storage::VertexAccessor" [color=black];
|
||||
"query::TypedValue" -> "storage::EdgeAccessor" [color=black];
|
||||
"query::Path" -> "storage::VertexAccessor"
|
||||
[label="vertices" dir=both arrowtail=diamond arrowhead=vee color=black];
|
||||
"query::Path" -> "storage::EdgeAccessor"
|
||||
[label="edges" dir=both arrowtail=diamond arrowhead=vee color=black];
|
||||
"query::plan::LogicalOperator" -> "database::GraphDbAccessor"
|
||||
[color=black arrowhead=vee];
|
||||
// cluster_distributed -- cluster_storage
|
||||
"distributed::DataManager" -> "database::GraphDb"
|
||||
[arrowhead=vee style=dashed color=red];
|
||||
"distributed::DataManager" -> "tx::TransactionId"
|
||||
[label="ves_caches_key" dir=both arrowhead=none arrowtail=diamond
|
||||
color=red];
|
||||
"distributed::DataManager" -> "storage::Vertex"
|
||||
[label="vertices_caches" dir=both arrowhead=none arrowtail=diamond
|
||||
color=red];
|
||||
"distributed::DataManager" -> "storage::Edge"
|
||||
[label="edges_caches" dir=both arrowhead=none arrowtail=diamond
|
||||
color=red];
|
||||
// cluster_storage -- cluster_properties_on_disk
|
||||
"PropertyValueStore" -> "storage::KVStore"
|
||||
[label="static" arrowhead=vee color=black];
|
||||
// cluster_dynamic_partitioning -- cluster_storage
|
||||
"database::GraphDb" -> "DynamicPartitioner"
|
||||
[arrowhead=vee color=red];
|
||||
"DynamicPartitioner" -> "database::GraphDbAccessor"
|
||||
[arrowhead=vee color=black];
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
digraph {
|
||||
// label="Dynamig Graph Partitioning";
|
||||
fontname="Roboto Bold"; fontcolor=black;
|
||||
fontsize=26; labelloc=top; labeljust=center;
|
||||
compound=true; // If true, allow edges between clusters
|
||||
rankdir=TB; // Alternatives: LR
|
||||
node [shape=record fontname="Roboto", fontsize=12, fontcolor=white
|
||||
style=filled, color="#FB6E00" fillcolor="#FB6E00"];
|
||||
edge [color="#B5AFB7"];
|
||||
|
||||
"distributed::DistributedGraphDb" -> "distributed::TokenSharingRpcServer";
|
||||
|
||||
"distributed::TokenSharingRpcServer" -> "communication::rpc::Server";
|
||||
"distributed::TokenSharingRpcServer" -> "distributed::Coordination";
|
||||
"distributed::TokenSharingRpcServer" -> "distributed::TokenSharingRpcClients";
|
||||
"distributed::TokenSharingRpcServer" -> "distributed::dgp::Partitioner";
|
||||
|
||||
"distributed::dgp::Partitioner" -> "distributed::DistributedGraphDb" [style=dashed];
|
||||
|
||||
"distributed::dgp::Partitioner" -> "distributed::dgp::VertexMigrator";
|
||||
"distributed::dgp::VertexMigrator" -> "database::GraphDbAccessor" [style=dashed];
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
# Distributed addressing
|
||||
|
||||
In distributed Memgraph a single graph element must be owned by exactly
|
||||
one worker. It is possible that multiple workers have cached copies of
|
||||
a single graph element (which is inevitable), but there is only one
|
||||
owner.
|
||||
|
||||
The owner of a graph element can change. This is not yet implemented,
|
||||
but is intended. Graph partitioning is intended to be dynamic.
|
||||
|
||||
Graph elements refer to other graph elements that are possibly on some
|
||||
other worker. Even though each graph element is identified with a unique
|
||||
ID, that ID does not contain the information about where that element
|
||||
currently resides (which worker is the owner).
|
||||
|
||||
Thus we introduce the concept of a global address. It indicates both
|
||||
which graph element is referred to (it's global ID), and where it
|
||||
resides. Semantically it's a pair of two elements, but for efficiency
|
||||
it's stored in 64 bits.
|
||||
|
||||
The global address is efficient for usage in a cluster: it indicates
|
||||
where something can be found. However, finding a graph element based on
|
||||
it's ID is still not a free operation (in the current implementation
|
||||
it's a skiplist lookup). So, whenever possible, it's better to use local
|
||||
addresses (pointers).
|
||||
|
||||
Succinctly, the requirements for addressing are:
|
||||
- global addressing containing location info
|
||||
- fast local addressing
|
||||
- storage of both types in the same location efficiently
|
||||
- translation between the two
|
||||
|
||||
The `storage::Address` class handles the enumerated storage
|
||||
requirements. It stores either a local or global address in the size of
|
||||
a local pointer (typically 8 bytes).
|
||||
|
||||
Conversion between the two is done in multiple places. The general
|
||||
approach is to use local addresses (when possible) only for local
|
||||
in-memory handling. All the communication and persistence uses global
|
||||
addresses. Also, when receiving address from another worker, attempt to
|
||||
localize addresses as soon as possible, so that least code has to worry
|
||||
about potential inefficiency of using a global address for a local graph
|
||||
element.
|
||||
@@ -1,50 +0,0 @@
|
||||
# Distributed durability
|
||||
|
||||
Durability in distributed is slightly different then in single-node as
|
||||
the state itself is shared between multiple workers and none of those
|
||||
states are independent.
|
||||
|
||||
Note that recovering from persistent storage must result in a stable
|
||||
database state. This means that across the cluster the state
|
||||
modification of every transaction that was running is either recovered
|
||||
fully or not at all. Also, if transaction A committed before transaction B,
|
||||
then if B is recovered so must A.
|
||||
|
||||
## Snapshots
|
||||
|
||||
It is possibly avoidable but highly desirable that the database can be
|
||||
recovered from snapshot only, without relying on WAL files. For this to
|
||||
be possible in distributed, it must be ensured that the same
|
||||
transactions are recovered on all the workers (including master) in the
|
||||
cluster. Since the snapshot does not contain information about which
|
||||
state change happened in which transaction, the only way to achieve this
|
||||
is to have synchronized snapshots. This means that the process of
|
||||
creating a snapshot, which is in itself transactional (it happens within
|
||||
a transaction and thus observes some consistent database state), must
|
||||
happen in the same transaction. This is achieved by the master starting
|
||||
a snapshot generating transaction and triggering the process on all
|
||||
workers in the cluster.
|
||||
|
||||
## WAL
|
||||
|
||||
Unlike the snapshot, write-ahead logs contain the information on which
|
||||
transaction made which state change. This makes it possible to include
|
||||
or exclude transactions during the recovery process. What is necessary
|
||||
however is a global consensus on which of the transactions should be
|
||||
recovered and which not, to ensure recovery into a consistent state.
|
||||
|
||||
It would be possible to achieve this with some kind of synchronized
|
||||
recovery process, but it would impose constraints on cluster startup and
|
||||
would not be trivial.
|
||||
|
||||
A simpler alternative is that the consensus is achieved beforehand,
|
||||
while the database (to be recovered) is still operational. What is
|
||||
necessary is to keep track of which transactions are guaranteed to
|
||||
have been flushed to the WAL files on all the workers in the cluster. It
|
||||
makes sense to keep this record on the master, so a mechanism is
|
||||
introduced which periodically pings all the workers, telling them to
|
||||
flush their WALs, and writes some sort of a log indicating that this has
|
||||
been confirmed. The downside of this is a periodic broadcast must be
|
||||
done, and that potentially slightly less data can be recovered in the
|
||||
case of a crash then if using a post-crash consensus. It is however much
|
||||
simpler to implement.
|
||||
@@ -1,51 +0,0 @@
|
||||
## Dynamic Graph Partitioning
|
||||
|
||||
Memgraph supports dynamic graph partitioning similar to the Spinner algorithm,
|
||||
mentioned in this paper: [https://arxiv.org/pdf/1404.3861.pdf].
|
||||
|
||||
Dgp is useful because it tries to group `local` date on the same worker, i.e.
|
||||
it tries to keep closely connected data on one worker. It tries to avoid jumps
|
||||
across workers when querying/traversing the distributed graph.
|
||||
|
||||
### Our implementation
|
||||
|
||||
It works independently on each worker but it is always running the migration
|
||||
on only one worker at the same time. It achieves that by sharing a token
|
||||
between workers, and the token ownership is transferred to the next worker
|
||||
when the current worker finishes its migration step.
|
||||
|
||||
The reason that we want workers to work in disjoint time slots is it avoid
|
||||
serialization errors caused by creating/removing edges of vertices during
|
||||
migrations, which might cause an update of some vertex from two or more
|
||||
different transactions.
|
||||
|
||||
### Migrations
|
||||
|
||||
For each vertex and workerid (label in the context of Dgp algorithm) we define
|
||||
a score function. Score function takes into account labels of surrounding
|
||||
endpoints of vertex edges (in/out) and the capacity of the worker with said
|
||||
label. Score function loosely looks like this
|
||||
```
|
||||
locality(v, l) =
|
||||
count endpoints of edges of vertex `v` with label `l` / degree of `v`
|
||||
|
||||
capacity(l) =
|
||||
number of vertices on worker `l` divided by the worker capacity
|
||||
(usually equal to the average number of vertices per worker)
|
||||
|
||||
score(v, l) = locality(v, l) - capacity(l)
|
||||
```
|
||||
We also define two flags alongside ```dynamic_graph_partitioner_enabled```,
|
||||
```dgp_improvement_threshold``` and ```dgp_max_batch_size```.
|
||||
|
||||
These two flags are used during the migration phase.
|
||||
When deciding if we need to migrate some vertex `v` from worker `l1` to worker
|
||||
`l2` we examine the difference in scores, i.e.
|
||||
if score(v, l1) - dgp_improvement_threshold / 100 < score(v, l2) then we
|
||||
migrate the vertex.
|
||||
|
||||
Max batch size flag limits the number of vertices we can transfer in one batch
|
||||
(one migration step).
|
||||
Setting this value to a too large value will probably cause
|
||||
a lot of interference with client queries, and having it a small value
|
||||
will slow down convergence of the algorithm.
|
||||
@@ -1,54 +0,0 @@
|
||||
# Memgraph distributed
|
||||
|
||||
This chapter describes some of the concepts used in distributed
|
||||
Memgraph. By "distributed" here we mean the sharding of a single graph
|
||||
onto multiple processing units (servers).
|
||||
|
||||
## Conceptual organization
|
||||
|
||||
There is a single master and multiple workers. The master contains all
|
||||
the global sources of truth (transaction engine,
|
||||
[label|edge-type|property] to name mappings). Also, in the current
|
||||
organization it is the only one that contains a Bolt server (for
|
||||
communication with the end client) and an interpretation engine. Workers
|
||||
contain the data and means of subquery interpretation (query plans
|
||||
recieved from the master) and means of communication with the master and
|
||||
other workers.
|
||||
|
||||
In many query plans the load on the master is much larger then the load
|
||||
on the workers. For that reason it might be beneficial to make the
|
||||
master contain less data (or none at all), and/or having multiple
|
||||
interpretation masters.
|
||||
|
||||
## Logic organization
|
||||
|
||||
Both the distributed and the single node Memgraph use the same codebase.
|
||||
In cases where the behavior in single-node differs from that in
|
||||
distributed, some kind of dynamic behavior change is implemented (either
|
||||
through inheritance or conditional logic).
|
||||
|
||||
### GraphDb
|
||||
|
||||
The `database::GraphDb` is an "umbrella" object for parts of the
|
||||
database such as storage, garbage collection, transaction engine etc.
|
||||
There is a class heirarchy of `GraphDb` implementations, as well as a
|
||||
base interface object. There are subclasses for single-node, master and
|
||||
worker deplotyments. Which implementation is used depends on the
|
||||
configuration processed in the `main` entry point of memgraph.
|
||||
|
||||
The `GraphDb` interface exposes getters to base classes of
|
||||
other similar heirarchies (for example to `tx::Engine`). In that way
|
||||
much of the code that uses those objects (for example query plan
|
||||
interpretation) is agnostic to the type of deployment.
|
||||
|
||||
### RecordAccessors
|
||||
|
||||
The functionality of `RecordAccessors` and it's subclasses is already
|
||||
documented. It's important to note that the same implementation of
|
||||
accessors is used in all deployments, with internal changes of behavior
|
||||
depending on the locality of the graph element (vertex or edge) the
|
||||
accessor represents. For example, if the graph element is local, an
|
||||
update operation on an accessor will make the necessary MVCC ops, update
|
||||
local data, indexes, the write-ahead log etc. However, if the accessor
|
||||
represents a remote graph element, an update will trigger an RPC message
|
||||
to the owner about the update and a change in the local cache.
|
||||
@@ -1,103 +0,0 @@
|
||||
# Distributed updates
|
||||
|
||||
Operations that modify the graph state are somewhat more complex in the
|
||||
distributed system, as opposed to a single-node Memgraph deployment. The
|
||||
complexity arises from two factors.
|
||||
|
||||
First, the data being modified is not necessarily owned by the worker
|
||||
performing the modification. This situation is completely valid workers
|
||||
execute parts of the query plan and parts must be executed by the
|
||||
master.
|
||||
|
||||
Second, there are less guarantees regarding multi-threaded access. In
|
||||
single-node Memgraph it was guaranteed that only one transaction will be
|
||||
performing database work in a single transaction. This implied that
|
||||
per-version storage could be thread-unsafe. In distributed Memgraph it
|
||||
is possible that multiple threads could be performing work in the same
|
||||
transaction as a consequence of the query being executed at the same
|
||||
time on multiple workers and those executions interacting with the
|
||||
globally partitioned database state.
|
||||
|
||||
## Deferred state modification
|
||||
|
||||
Making the per-version data storage thread-safe would most likely have a
|
||||
performance impact very undesirable in a transactional database intended
|
||||
for high throughput.
|
||||
|
||||
An alternative is that state modification over unsafe structures is not
|
||||
performed immediately when requested, but postponed until it is safe to
|
||||
do (there is is a guarantee of no concurrent access).
|
||||
|
||||
Since local query plan execution is done the same way on local data as
|
||||
it is in single-node Memgraph, it is not possible to deffer that part of
|
||||
the modification story. What can be deferred are modifications requested
|
||||
by other workers. Since local query plan execution still is
|
||||
single-threaded, this approach is safe.
|
||||
|
||||
At the same time those workers requesting the remote update can update
|
||||
local copies (caches) of the not-owned data since that cache is only
|
||||
being used by the single, local-execution thread.
|
||||
|
||||
### Visibility
|
||||
|
||||
Since updates are deferred the question arises: when do the updates
|
||||
become visible? The above described process offers the following
|
||||
visibility guarantees:
|
||||
- updates done on the local state are visible to the owner
|
||||
- updates done on the local state are NOT visible to anyone else during
|
||||
the same (transaction + command)
|
||||
- updates done on remote state are deferred on the owner and not
|
||||
visible to the owner until applied
|
||||
- updates done on the remote state are applied immediately to the local
|
||||
caches and thus visible locally
|
||||
|
||||
This implies an inconsistent view of the database state. In a concurrent
|
||||
execution of a single query this can hardly be avoided and is accepted
|
||||
as such. It does not change the Cypher query execution semantic in any
|
||||
of the well-defined scenarios. It possibly changes some of the behaviors
|
||||
in which the semantic is not well defined even in single-node execution.
|
||||
|
||||
### Synchronization, update application
|
||||
|
||||
In many queries it is mandatory to observe the latest global graph state
|
||||
(typically when returning it to the client). That means that before that
|
||||
happens all the deferred updates need to be applied, and all the caches
|
||||
to remote data invalidated. Exactly this happens when executing queries
|
||||
that modify the graph state. At some point a global synchronization
|
||||
point is reached. First it is waited that all workers finish the
|
||||
execution of query plan parts performing state modifications. After that
|
||||
all the workers are told to apply the deferred updates they received to
|
||||
their graph state. Since there is no concurrent query plan execution,
|
||||
this is safe. Once that is done all the local caches are cleared and the
|
||||
requested data can be returned to the client.
|
||||
|
||||
### Command advancement
|
||||
|
||||
In complex queries where a read part follows a state modification part
|
||||
the synchronization process after the state modification part is
|
||||
followed by command advancement, like in single-node execution.
|
||||
|
||||
## Creation
|
||||
|
||||
Graph element creation is not deferred. This is practical because the
|
||||
response to a creation is the global ID of the newly created element. At
|
||||
the same time it is safe because no other worker (including the owner)
|
||||
will be using the newly added graph element.
|
||||
|
||||
## Updating
|
||||
|
||||
Updating is deferred, as described. Note that this also means that
|
||||
record locking conflicts are deferred and serialization errors
|
||||
(including lock timeouts) are postponed until the deferred update
|
||||
application phase. In certain scenarios it might be beneficial to force
|
||||
these errors to happen earlier, when the deferred update request is
|
||||
processed.
|
||||
|
||||
## Deletion
|
||||
|
||||
Deletion is also deferred. Deleting an edge implies a modification of
|
||||
it's endpoint vertices, which must be deferred as those data structures
|
||||
are not thread-safe. Deleting a vertex is either with detaching, in
|
||||
which case an arbitrary number of updates are implied in the vertex's
|
||||
neighborhood, or without detaching which relies on checking the current
|
||||
state of the graph which is generally impossible in distributed.
|
||||
@@ -1,22 +0,0 @@
|
||||
# Snapshots
|
||||
|
||||
A "snapshot" is a record of the current database state stored in permanent
|
||||
storage. Note that the term "snapshot" is used also in the context of
|
||||
the transaction engine to denote a set of running transactions.
|
||||
|
||||
A snapshot is written to the file by Memgraph periodically if so
|
||||
configured. The snapshot creation process is done within a transaction created
|
||||
specifically for that purpose. The transaction is needed to ensure that
|
||||
the stored state is internally consistent.
|
||||
|
||||
The database state can be recovered from the snapshot during startup, if
|
||||
so configured. This recovery works in conjunction with write-ahead log
|
||||
recovery.
|
||||
|
||||
A single snapshot contains all the data needed to recover a database. In
|
||||
that sense snapshots are independent of each other and old snapshots can
|
||||
be deleted once the new ones are safely stored, if it is not necessary
|
||||
to revert the database to some older state.
|
||||
|
||||
The exact format of the snapshot file is defined inline in the snapshot
|
||||
creation code.
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
mkdir -p $script_dir/html
|
||||
|
||||
for markdown_file in $(find $script_dir -name '*.md'); do
|
||||
name=$(basename -s .md $markdown_file)
|
||||
sed -e 's/.md/.html/' $markdown_file | \
|
||||
pandoc -s -f markdown -t html -o $script_dir/html/$name.html
|
||||
done
|
||||
|
||||
for dot_file in $(find $script_dir -name '*.dot'); do
|
||||
name=$(basename -s .dot $dot_file)
|
||||
dot -Tpng $dot_file -o $script_dir/html/$name.png
|
||||
done
|
||||
BIN
docs/dev/query/interpreter-class.png
Normal file
BIN
docs/dev/query/interpreter-class.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
@@ -31,14 +31,6 @@ the following steps.
|
||||
After the generation, the execution cost of each plan is estimated. This
|
||||
estimation is used to select the best plan which will be executed.
|
||||
|
||||
5. [Distributed Planning](#distributed-planning)
|
||||
|
||||
In case we are running distributed Memgraph, the final plan is adapted
|
||||
for distributed execution. NOTE: This appears to be an error in the
|
||||
workflow. Distributed planning should be moved before step 3. or
|
||||
integrated with it. With the workflow ordered as is now, cost estimation
|
||||
doesn't consider the distributed plan.
|
||||
|
||||
The implementation can be found in the `query/plan` directory, with the public
|
||||
entry point being `query/plan/planner.hpp`.
|
||||
|
||||
@@ -476,12 +468,59 @@ finally for this example, the `Merge` would have:
|
||||
|
||||
## Logical Plan Postprocessing
|
||||
|
||||
NOTE: TODO
|
||||
Postprocessing of a logical plan is done by rewriting the original plan into
|
||||
a more efficient one while preserving the original semantic of operations.
|
||||
The rewriters are found in `query/plan/rewrite` directory, and currently we
|
||||
only have one -- `IndexLookupRewriter`.
|
||||
|
||||
### IndexLookupRewriter
|
||||
|
||||
The job of this rewriter is to merge `Filter` and `ScanAll` operations into
|
||||
equivalent `ScanAllBy<Index>` operations. In almost all cases using indexed
|
||||
lookup will be faster than regular lookup, so `IndexLookupRewriter` simply
|
||||
does the transformations whenever possible. The simplest case being the
|
||||
following, assuming we have an index over `id`.
|
||||
|
||||
* Original Plan
|
||||
|
||||
`ScanAll (n) > Filter (id(n) == 42) > Produce (n)`
|
||||
|
||||
* Rewritten Plan
|
||||
|
||||
`ScanAllById (n, id=42) > Produce (n)`
|
||||
|
||||
Naturally, there are some cases we need to be careful about.
|
||||
|
||||
1. Operators with Multiple Branches
|
||||
|
||||
Here we may not carry `Filter` operations outside of the operator into
|
||||
its branches, so the branches are rewritten as stand alone plans with a
|
||||
branch new `IndexLookupRewriter`. Some of the operators with multiple
|
||||
branches are `Merge`, `Optional`, `Cartesian` and `Union`.
|
||||
|
||||
2. Expand Operators
|
||||
|
||||
Expand operations aren't that tricky to handle, but they have a special
|
||||
case where we want to use an indexed lookup of the destination so that the
|
||||
expansion is performed between known nodes. This decision may depend on
|
||||
various parameters which may need further tweaking as we encounter more
|
||||
use-cases of Cypher queries.
|
||||
|
||||
## Cost Estimation
|
||||
|
||||
NOTE: TODO
|
||||
Cost estimation is the final step of processing a logical plan. The
|
||||
implementation can be found in `query/plan/cost_estimator.hpp`. We give each
|
||||
operator a cost based on the estimated cardinality of results of that operator
|
||||
and on the preset coefficient of the runtime performance of that operator.
|
||||
|
||||
## Distributed Planning
|
||||
This scheme is rather simple and works quite well, but there are couple of
|
||||
improvements we may want to do at some point.
|
||||
|
||||
NOTE: TODO
|
||||
* Track more information about the stored graph and use that to improve the
|
||||
estimates.
|
||||
* Do a quick, partial run of the plan and tweak the estimation based on how
|
||||
much each operator produced results. This may require us having some kind
|
||||
of representative subset of the stored graph.
|
||||
* Write micro benchmarks for each operator and based on the results create
|
||||
sensible preset coefficients. This would replace the current coefficients
|
||||
which are just assumptions on how each operator implementation performs.
|
||||
|
||||
@@ -10,14 +10,12 @@ on your machine before you can download the source code.
|
||||
On Debian systems, you can do it inside a terminal with the following
|
||||
command:
|
||||
|
||||
sudo apt-get install git
|
||||
|
||||
On ArchLinux or Gentoo, you probably already know what to do.
|
||||
apt install git
|
||||
|
||||
After installing `git`, you are now ready to fetch your own copy of Memgraph
|
||||
source code. Run the following command:
|
||||
|
||||
git clone https://phabricator.memgraph.io/diffusion/MG/memgraph.git
|
||||
git clone https://github.com/memgraph/memgraph.git
|
||||
|
||||
The above will create a `memgraph` directory and put all source code there.
|
||||
|
||||
@@ -32,19 +30,21 @@ In your terminal, position yourself in the obtained memgraph directory.
|
||||
|
||||
### Installing Dependencies
|
||||
|
||||
On Debian systems, dependencies that are required by the codebase should be
|
||||
setup by running the `init` script:
|
||||
|
||||
./init -s
|
||||
|
||||
Currently, other systems aren't supported in the `init` script. But you can
|
||||
issue the needed steps manually. First run the `init` script.
|
||||
Dependencies that are required by the codebase should be checked by running the
|
||||
`init` script:
|
||||
|
||||
./init
|
||||
|
||||
The script will output the required packages, which you should be able to
|
||||
install via your favorite package manager. For example, `pacman` on ArchLinux.
|
||||
After installing the packages, issue the following commands:
|
||||
If the script fails, dependencies installation scripts could be found under
|
||||
`environment/os/`. The directory contains dependencies management script for
|
||||
each supported operating system. E.g. if your system is **Debian 10**, run the
|
||||
following to install all required build packages:
|
||||
|
||||
./environment/os/debian-10.sh install MEMGRAPH_BUILD_DEPS
|
||||
|
||||
Once everything is installed, rerun the `init` script.
|
||||
|
||||
Once the `init` script is successfully finished, issue the following commands:
|
||||
|
||||
mkdir -p build
|
||||
./libs/setup.sh
|
||||
@@ -52,18 +52,17 @@ After installing the packages, issue the following commands:
|
||||
### Compiling
|
||||
|
||||
Memgraph is compiled using our own custom toolchain that can be obtained from
|
||||
[Toolchain repository](https://deps.memgraph.io/toolchain). You should read
|
||||
the `README.txt` file in the repository and install the apropriate toolchain
|
||||
for your distribution. After you have installed the toolchain you should read
|
||||
the instructions for the toolchain in the toolchain install directory
|
||||
(`/opt/toolchain-vXYZ/README.md`) and install dependencies that are necessary
|
||||
to run the toolchain.
|
||||
the toolchain repository. You should read the `environment/README.txt` file
|
||||
in the repository and install the apropriate toolchain for your distribution.
|
||||
After you have installed the toolchain you should read the instructions for the
|
||||
toolchain in the toolchain install directory (`/opt/toolchain-vXYZ/README.md`)
|
||||
and install dependencies that are necessary to run the toolchain.
|
||||
|
||||
When you want to compile Memgraph you should activate the toolchain using the
|
||||
prepared toolchain activation script that is also described in the toolchain
|
||||
`README`.
|
||||
|
||||
NOTE: You *must* activate the toolchain every time you want to compile
|
||||
NOTE: You **must** activate the toolchain every time you want to compile
|
||||
Memgraph!
|
||||
|
||||
You should now activate the toolchain in your console.
|
||||
|
||||
@@ -38,7 +38,7 @@ interpretation code) should work with accessors. There is a
|
||||
`RecordAccessor` as a base class for `VertexAccessor` and
|
||||
`EdgeAccessor`. Following is an enumeration of their purpose.
|
||||
|
||||
### Data access
|
||||
### Data Access
|
||||
|
||||
The client interacts with Memgraph using the Cypher query language. That
|
||||
language has certain semantics which imply that multiple versions of the
|
||||
@@ -75,7 +75,7 @@ In distributed Memgraph accessors also contain a lot of the remote graph
|
||||
element handling logic. More info on that is available in the
|
||||
documentation for distributed.
|
||||
|
||||
### Deferred MVCC data lookup for Edges
|
||||
### Deferred MVCC Data Lookup for Edges
|
||||
|
||||
Vertices and edges are versioned using MVCC. This means that for each
|
||||
transaction an MVCC lookup needs to be done to determine which version
|
||||
@@ -100,7 +100,7 @@ working with that data directly whenever possible! Always consider the
|
||||
accessors to be the first go-to for interacting with data, especially
|
||||
when in the context of a transaction.
|
||||
|
||||
# Skiplist accessor
|
||||
# Skiplist Accessor
|
||||
|
||||
The term "accessor" is also used in the context of a skiplist. Every
|
||||
operation on a skiplist must be performed within on an
|
||||
6
docs/dev/storage/v1/contents.md
Normal file
6
docs/dev/storage/v1/contents.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Storage v1
|
||||
|
||||
* [Accessors](accessors.md)
|
||||
* [Indexes](indexes.md)
|
||||
* [Property Storage](property-storage.md)
|
||||
* [Durability](durability.md)
|
||||
@@ -1,4 +1,6 @@
|
||||
# Write-ahead logging
|
||||
# Durability
|
||||
|
||||
## Write-ahead Logging
|
||||
|
||||
Typically WAL denotes the process of writing a "log" of database
|
||||
operations (state changes) to persistent storage before committing the
|
||||
@@ -53,3 +55,26 @@ inefficient) to recover the database from WAL only, provided all the WAL
|
||||
files created from DB start are available. It is not possible to recover
|
||||
partial database state (i.e. from some suffix of WAL files, without the
|
||||
preceding snapshot).
|
||||
|
||||
## Snapshots
|
||||
|
||||
A "snapshot" is a record of the current database state stored in permanent
|
||||
storage. Note that the term "snapshot" is used also in the context of
|
||||
the transaction engine to denote a set of running transactions.
|
||||
|
||||
A snapshot is written to the file by Memgraph periodically if so
|
||||
configured. The snapshot creation process is done within a transaction created
|
||||
specifically for that purpose. The transaction is needed to ensure that
|
||||
the stored state is internally consistent.
|
||||
|
||||
The database state can be recovered from the snapshot during startup, if
|
||||
so configured. This recovery works in conjunction with write-ahead log
|
||||
recovery.
|
||||
|
||||
A single snapshot contains all the data needed to recover a database. In
|
||||
that sense snapshots are independent of each other and old snapshots can
|
||||
be deleted once the new ones are safely stored, if it is not necessary
|
||||
to revert the database to some older state.
|
||||
|
||||
The exact format of the snapshot file is defined inline in the snapshot
|
||||
creation code.
|
||||
@@ -1,10 +1,10 @@
|
||||
# Label indexes
|
||||
# Label Indexes
|
||||
|
||||
These are unsorted indexes that contain all the vertices that have the label
|
||||
the indexes are for (one index per label). These kinds of indexes get
|
||||
automatically generated for each label used in the database.
|
||||
|
||||
### Updating the indexes
|
||||
### Updating the Indexes
|
||||
|
||||
Whenever something gets added to the record we update the index (add that
|
||||
record to index). We keep an index which might contain garbage (not relevant
|
||||
@@ -64,7 +64,7 @@ same order, with (record, vlist) pair
|
||||
already superseded by a newer record and as such won't be inserted while
|
||||
it's being deleted
|
||||
|
||||
### Querying the index
|
||||
### Querying the Index
|
||||
|
||||
We run through the index for the given label and do `vlist.find` operation for
|
||||
the current transaction, and check if the newest return record has that
|
||||
@@ -75,7 +75,7 @@ in the index are sorted by their `vlist*` and as such we can filter consecutive
|
||||
duplicate `vlist*` to only return one of those while still being able to create
|
||||
an iterator to index.
|
||||
|
||||
### Cleaning the index
|
||||
### Cleaning the Index
|
||||
|
||||
Cleaning the index is not as straightforward as it seems as a lot of garbage
|
||||
can accumulate, but it's hard to know when exactly can we delete some (record,
|
||||
@@ -1,7 +1,7 @@
|
||||
# Property storage
|
||||
# Property Storage
|
||||
|
||||
Although the reader is probably familiar with properties in *Memgraph*, let's
|
||||
briefly recap.
|
||||
briefly recap.
|
||||
|
||||
Both vertices and edges can store an arbitrary number of properties. Properties
|
||||
are, in essence, ordered pairs of property names and property values. Each
|
||||
@@ -21,9 +21,9 @@ must be one of the following types:
|
||||
|
||||
Property values are modeled in a class conveniently called `PropertyValue`.
|
||||
|
||||
## Mapping between property names and property keys.
|
||||
## Mapping Between Property Names and Property Keys.
|
||||
|
||||
Although users think of property names in terms of descriptive strings
|
||||
Although users think of property names in terms of descriptive strings
|
||||
(e.g. "location" or "department"), *Memgraph* internally converts those names
|
||||
into property keys which are, essentially, unsigned 16-bit integers.
|
||||
|
||||
@@ -49,7 +49,7 @@ An interface of `PropertyValueStore` is as follows:
|
||||
`clear` | Clears the storage.
|
||||
`iterator`| Provides an extension of `std::input_iterator` that iterates over storage.
|
||||
|
||||
## Storage location
|
||||
## Storage Location
|
||||
|
||||
By default, *Memgraph* is an in-memory database and all properties are therefore
|
||||
stored in working memory unless specified otherwise by the user. User has an
|
||||
@@ -63,12 +63,12 @@ property key has the following format:
|
||||
```
|
||||
|---location--|------id------|
|
||||
|-Memory|Disk-|-----2^15-----|
|
||||
```
|
||||
```
|
||||
|
||||
In other words, the most significant bit determines the location where the
|
||||
property will be stored.
|
||||
property will be stored.
|
||||
|
||||
### In-memory storage
|
||||
### In-memory Storage
|
||||
|
||||
The underlying implementation of in-memory storage for the time being is
|
||||
`std::vector<std::pair<Property, PropertyValue>>`. Implementations of`at`, `set`
|
||||
@@ -76,7 +76,7 @@ and `erase` are linear in time. This implementation is arguably more efficient
|
||||
than `std::map` or `std::unordered_map` when the average number of properties of
|
||||
a record is relatively small (up to 10) which seems to be the case.
|
||||
|
||||
### On-disk storage
|
||||
### On-disk Storage
|
||||
|
||||
#### KVStore
|
||||
|
||||
@@ -120,12 +120,12 @@ KVStore storage = ...;
|
||||
for (auto it = storage.begin("alpha"); it != storage.end("omega"); ++it) {}
|
||||
```
|
||||
|
||||
#### Data organization on disk
|
||||
#### Data Organization on Disk
|
||||
|
||||
Each `PropertyValueStore` instance can access a static `KVStore` object that can
|
||||
store `(key, value)` pairs on disk. The key of each property on disk consists of
|
||||
two parts — a unique identifier (unsigned 64-bit integer) of the current
|
||||
record version (see mvcc docummentation for further clarification) and a
|
||||
record version (see mvcc docummentation for further clarification) and a
|
||||
property key as described above. The actual value of the property is serialized
|
||||
into a bytestring using bolt `BaseEncoder`. Similarly, deserialization is
|
||||
performed by bolt `Decoder`.
|
||||
3
docs/dev/storage/v2/contents.md
Normal file
3
docs/dev/storage/v2/contents.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Storage v2
|
||||
|
||||
TODO(gitbuda): Write documentation.
|
||||
@@ -1,152 +0,0 @@
|
||||
# Bootstrapping Compilation Toolchain for Memgraph
|
||||
|
||||
Requirements:
|
||||
|
||||
* libstdc++ shipped with gcc-6.3 or gcc-6.4
|
||||
* cmake >= 3.1, Debian Stretch uses cmake-3.7.2
|
||||
* clang-3.9
|
||||
|
||||
## Installing gcc-6.4
|
||||
|
||||
gcc-6.3 has a bug, so use the 6.4 version which is just a bugfix release.
|
||||
|
||||
Requirements on CentOS 7:
|
||||
|
||||
* wget
|
||||
* make
|
||||
* gcc (bootstrap)
|
||||
* gcc-c++ (bootstrap)
|
||||
* gmp-devel (bootstrap)
|
||||
* mpfr-devel (bootstrap)
|
||||
* libmpc-devel (bootstrap)
|
||||
* zip
|
||||
* perl
|
||||
* dejagnu (testing)
|
||||
* expect (testing)
|
||||
* tcl (testing)
|
||||
|
||||
```
|
||||
wget ftp://ftp.mpi-sb.mpg.de/pub/gnu/mirror/gcc.gnu.org/pub/gcc/releases/gcc-6.4.0/gcc-6.4.0.tar.gz
|
||||
tar xf gcc-6.4.0.tar.gz
|
||||
cd gcc-6.4.0
|
||||
mkdir build
|
||||
cd build
|
||||
../configure --disable-multilib --prefix=<install-dst>
|
||||
make
|
||||
# Testing
|
||||
make -k check
|
||||
make install
|
||||
```
|
||||
|
||||
*Do not put gcc + libs on PATH* (unless you know what you are doing).
|
||||
|
||||
## Installing cmake-3.7.2
|
||||
|
||||
Requirements on CentOS 7:
|
||||
|
||||
* wget
|
||||
* make
|
||||
* gcc
|
||||
* gcc-c++
|
||||
* ncurses-devel (optional, for ccmake)
|
||||
|
||||
```
|
||||
wget https://cmake.org/files/v3.7/cmake-3.7.2.tar.gz
|
||||
tar xf cmake-3.7.2.tar.gz
|
||||
cd cmake-3.7.2.tar.gz
|
||||
./bootstrap --prefix<install-dst>
|
||||
make
|
||||
make install
|
||||
```
|
||||
|
||||
Put cmake on PATH (if appropriate)
|
||||
|
||||
**Fix the bug in CpackRPM**
|
||||
|
||||
`"<path-to-cmake>/share/cmake-3.7/Modules/CPackRPM.cmake" line 2273 of 2442`
|
||||
|
||||
The line
|
||||
|
||||
```
|
||||
set(RPMBUILD_FLAGS "-bb")
|
||||
```
|
||||
needs to be before
|
||||
|
||||
```
|
||||
if(CPACK_RPM_GENERATE_USER_BINARY_SPECFILE_TEMPLATE OR NOT CPACK_RPM_USER_BINARY_SPECFILE)
|
||||
```
|
||||
|
||||
It was probably accidentally placed after, and is fixed in later cmake
|
||||
releases.
|
||||
|
||||
## Installing clang-3.9
|
||||
|
||||
Requirements on CentOS 7:
|
||||
|
||||
* wget
|
||||
* make
|
||||
* cmake
|
||||
|
||||
```
|
||||
wget http://releases.llvm.org/3.9.1/llvm-3.9.1.src.tar.xz
|
||||
tar xf llvm-3.9.1.src.tar.xz
|
||||
mv llvm-3.9.1.src llvm
|
||||
|
||||
wget http://releases.llvm.org/3.9.1/cfe-3.9.1.src.tar.xz
|
||||
tar xf cfe-3.9.1.src.tar.xz
|
||||
mv cfe-3.9.1.src llvm/tools/clang
|
||||
|
||||
cd llvm
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE="Release" -DGCC_INSTALL_PREFIX=<gcc-dir> \
|
||||
-DCMAKE_C_COMPILER=<gcc> -DCMAKE_CXX_COMPILER=<g++> \
|
||||
-DCMAKE_CXX_LINK_FLAGS="-L<gcc-dir>/lib64 -Wl,-rpath,<gcc-dir>/lib64" \
|
||||
-DCMAKE_INSTALL_PREFIX=<install-dst> ..
|
||||
make
|
||||
# Testing
|
||||
make check-clang
|
||||
make install
|
||||
```
|
||||
|
||||
Put clang on PATH (if appropriate)
|
||||
|
||||
## Memgraph
|
||||
|
||||
Requirements on CentOS 7:
|
||||
|
||||
* libuuid-devel (antlr4)
|
||||
* java-1.8.0-openjdk (antlr4)
|
||||
* boost-static (too low version --- compile manually)
|
||||
* rpm-build (RPM)
|
||||
* python3 (tests, ...)
|
||||
* which (required for rocksdb)
|
||||
* sbcl (lisp C++ preprocessing)
|
||||
|
||||
### Boost 1.62
|
||||
|
||||
```
|
||||
wget https://netix.dl.sourceforge.net/project/boost/boost/1.62.0/boost_1_62_0.tar.gz
|
||||
tar xf boost_1_62_0.tar.gz
|
||||
cd boost_1_62_0
|
||||
./bootstrap.sh --with-toolset=clang --with-libraries=iostreams,serialization --prefix=<install-dst>
|
||||
./b2
|
||||
# Default installs to /usr/local/
|
||||
./b2 install
|
||||
```
|
||||
|
||||
### Building Memgraph
|
||||
|
||||
clang is *required* to be findable by cmake, i.e. it should be on PATH.
|
||||
cmake isn't required to be on the path, since you run it manually, so can use
|
||||
the full path to executable in order to run it. Obviously, it is convenient to
|
||||
put cmake also on PATH.
|
||||
|
||||
Building is done as explained in [Quick Start](quick-start.md), but each
|
||||
`make` invocation needs to be prepended with:
|
||||
|
||||
`LD_RUN_PATH=<gcc-dir>/lib64 make ...`
|
||||
|
||||
### RPM
|
||||
|
||||
Name format: `memgraph-<version>-<pkg-version>.<arch>.rpm`
|
||||
@@ -20,54 +20,51 @@ Finally, make git aware of your favourite editor:
|
||||
|
||||
git config --global core.editor "vim"
|
||||
|
||||
## Phabricator
|
||||
## Github
|
||||
|
||||
All of the code in Memgraph needs to go through code review before it can be
|
||||
accepted in the codebase. This is done through
|
||||
[Phabricator](https://phacility.com/phabricator/). The command line tool for
|
||||
interfacing with Phabricator is
|
||||
[arcanist](https://phacility.com/phabricator/arcanist/). You should already
|
||||
have it installed if you followed the steps in [Quick Start](quick-start.md).
|
||||
|
||||
The only required setup is to go in the root of Memgraph's project and run:
|
||||
|
||||
arc install-certificate
|
||||
accepted in the codebase. This is done through [Github](https://github.com/).
|
||||
You should already have it installed if you followed the steps in [Quick
|
||||
Start](quick-start.md).
|
||||
|
||||
## Working on Your Feature Branch
|
||||
|
||||
Git has a concept of source code *branches*. The `master` branch contains all
|
||||
Git has a concept of source code **branches**. The `master` branch contains all
|
||||
of the changes which were reviewed and accepted in Memgraph's code base. The
|
||||
`master` branch is selected by default.
|
||||
|
||||
### Creating a Branch
|
||||
|
||||
When working on a new feature or fixing a bug, you should create a new branch
|
||||
out of the `master` branch. For example, let's say you are adding static type
|
||||
checking to the query language compiler. You would create a branch called
|
||||
`mg_query_static_typing` with the following command:
|
||||
out of the `master` branch. There are two branch types, **epic** and **task**
|
||||
branches. The epic branch is created when introducing a new feature or any work
|
||||
unit requiring more than one commit. More commits are required to split the
|
||||
work into chunks to be able to easier review code or find a bug (in each
|
||||
commit, there could be various problems, e.g., related to performance or
|
||||
concurrency issues, which are the hardest to track down). Each commit on the
|
||||
master or epic branch should be a compilable and well-documented set of
|
||||
changes. Task branches should be created when a smaller work unit has to be
|
||||
integrated into the codebase. The task branch could be branched out of the
|
||||
master or an epic branch. We manage epics and tasks on the project management
|
||||
tool called [Airtable](https://airtable.com/tblTUqycq8sHTTkBF). Each epic is
|
||||
prefixed by `Exyz-MG`, on the other hand, each task has `Tabcd-MG` prefix.
|
||||
Examples on how to create branches follow:
|
||||
|
||||
git branch mg_query_static_typing
|
||||
|
||||
To switch to that branch, type:
|
||||
|
||||
git checkout mg_query_static_typing
|
||||
|
||||
Since doing these two steps will happen often, you can use a shortcut command:
|
||||
|
||||
git checkout -b mg_query_static_typing
|
||||
```
|
||||
git checkout master
|
||||
git checkout -b T0025-MG-fix-a-problem
|
||||
...
|
||||
git checkout master
|
||||
git checkout -b E025-MG-huge-feature
|
||||
...
|
||||
git checkout E025-MG-huge-feature
|
||||
git checkout -b T0123-MG-add-feature-part
|
||||
```
|
||||
|
||||
Note that a branch is created from the currently selected branch. So, if you
|
||||
wish to create another branch from `master` you need to switch to `master`
|
||||
first.
|
||||
|
||||
The usual convention for naming your branches is `mg_<feature_name>`, you may
|
||||
switch underscores ('\_') for hyphens ('-').
|
||||
|
||||
Do take care not to mix the case of your branch names! Certain operating
|
||||
systems (like Windows) don't distinguish the casing in git branches. This may
|
||||
cause hard to track down issues when trying to switch branches. Therefore, you
|
||||
should always name your branches with lowercase letters.
|
||||
|
||||
### Making and Committing Changes
|
||||
|
||||
When you have a branch for your new addition, you can now actually start
|
||||
@@ -98,80 +95,72 @@ possible.
|
||||
### Sending Changes on a Review
|
||||
|
||||
After finishing your work on your feature branch, you will want to send it on
|
||||
code review. This is done through Arcanist. To do that, run the following
|
||||
command:
|
||||
code review. This is done by pushing the branch to Github and creating a pull
|
||||
request. You can find all PRs
|
||||
[here](https://github.com/memgraph/memgraph/pulls).
|
||||
|
||||
arc diff
|
||||
### Code Integration
|
||||
|
||||
You will, once again, be presented with an editor where you need to describe
|
||||
your whole work. `arc` will by default fill that description with your commit
|
||||
messages. The title and summary of your work should also follow the
|
||||
conventions of git messages as described above. If you followed the
|
||||
guidelines, the message filled by `arc` should be fine.
|
||||
When working, you have to integrate some changes to your work or push your work
|
||||
to be available for others. To pull changes into a local `branch`, usually run
|
||||
the following:
|
||||
|
||||
In addition to the message, you need to fill the `Reviewers:` line with
|
||||
usernames of people who should do the code review.
|
||||
git checkout {{branch}}
|
||||
git pull origin {{branch}}
|
||||
|
||||
You changes will be visible on Phabricator as a so called "diff". You can find
|
||||
the default view of active diffs
|
||||
[here](https://phabricator.memgraph.io/differential/)
|
||||
To push your changes, usually run the following:
|
||||
|
||||
### Updating Changes Based on Review
|
||||
git checkout {{branch}}
|
||||
git push origin {{branch}}
|
||||
|
||||
When you get comments in the code review, you will want to make additional
|
||||
modifications to your work. The same workflow as before applies: [Making and
|
||||
Committing Changes](#making-and-committing-changes)
|
||||
Sometimes, things could get a little bit more complicated. Diagram below shows
|
||||
which git operation should be performed if a piece of code has to be integrated
|
||||
from one branch to another. Note, `main_branch` is the **master** branch in our
|
||||
case.
|
||||
|
||||
After making those changes, send them back on code review:
|
||||
```
|
||||
|<---------------------------|
|
||||
| squash merge |
|
||||
|--------------------------->|
|
||||
| merge |
|
||||
| |
|
||||
|<-----------|<--------------|
|
||||
| merge | squash merge |
|
||||
| | |
|
||||
|----------->|-------------->|
|
||||
| rebase | merge |
|
||||
| | rebase --onto |
|
||||
| | |
|
||||
main_branch epic_branch task_branch
|
||||
```
|
||||
|
||||
arc diff
|
||||
There are a couple of cases:
|
||||
|
||||
* If a code has to be integrated from a task branch to the main branch, use
|
||||
**squash merge**. While you were working on a task, you probably committed a
|
||||
couple of cleanup commits that are not relevant to the main branch. In the
|
||||
other direction, while integrating the main branch to a task branch, the
|
||||
**regular merge** is ok because changes from the task branch will later be
|
||||
squash merged.
|
||||
|
||||
### Updating From New Master
|
||||
* You should use **squash merge** when integrating changes from task to epic
|
||||
branch (task might have irrelevant commits). On the other hand, you should
|
||||
use a **regular merge** when an epic is completed and has to be integrated into
|
||||
the main branch. Epic is a more significant piece of work, decoupled in
|
||||
compilable and testable commits. All these commits should be preserved to be
|
||||
able to find potential issues later on.
|
||||
|
||||
Let's say that, while you were working, someone else added some new features
|
||||
to the codebase that you would like to use in your current work. To obtain
|
||||
those changes you should update your `master` branch:
|
||||
* You should use **rebase** when integrating changes from main to an epic
|
||||
branch. The epic branch has to be as clean as possible, avoid pure merge
|
||||
commits. Once you rebase epic on main, all commits on the epic branch will
|
||||
change the hashes. The implications are: 1) you have to force push your local
|
||||
branch to the origin, 2) if you made a task branch out of the epic branch, you
|
||||
would have to use **rebase --onto** (please refer to `git help rebase` for
|
||||
details). In simple cases, **regular merge** should be sufficient to integrate
|
||||
changes from epic to a task branch (that can even be done via GitHub web
|
||||
interface).
|
||||
|
||||
git checkout master
|
||||
git pull origin master
|
||||
|
||||
Now, these changes are on `master`, but you want them in your local branch. To
|
||||
do that, use `git rebase`:
|
||||
|
||||
git checkout mg_query_static_typing
|
||||
git rebase master
|
||||
|
||||
During `git rebase`, you may get reports that some files have conflicting
|
||||
changes. If you need help resolving them, don't be afraid to ask around! After
|
||||
you've resolved them, mark them as done with `git add` command. You may
|
||||
then continue with `git rebase --continue`.
|
||||
|
||||
After the `git rebase` is done, you will now have new changes from `master` on
|
||||
your feature branch as if you just created and started working on that branch.
|
||||
You may continue with the usual workflow of [Making and Committing
|
||||
Changes](#making-and-committing-changes) and [Sending Changes on a
|
||||
Review](#sending-changes-on-a-review).
|
||||
|
||||
### Sending Your Changes on Master Branch
|
||||
|
||||
When your changes pass the code review, you are ready to integrate them in the
|
||||
`master` branch. To do that, run the following command:
|
||||
|
||||
arc land
|
||||
|
||||
Arcanist will take care of obtaining the latest changes from `master` and
|
||||
merging your changes on top. If the `land` was successful, Arcanist will
|
||||
delete your local branch and you will be back on `master`. Continuing from the
|
||||
examples above, the deleted branch would be `mg_query_static_typing`.
|
||||
|
||||
This marks the completion of your changes, and you are ready to work on
|
||||
something else.
|
||||
|
||||
### Note For People Familiar With Git
|
||||
|
||||
Since Arcanist takes care of merging your git commits and pushing them on
|
||||
`master`, you should *never* have to call `git merge` and `git push`. If you
|
||||
find yourself typing those commands, check that you are doing the right thing.
|
||||
The most common mistake is to use `git merge` instead of `git rebase` for the
|
||||
case described in [Updating From New Master](#updating-from-new-master).
|
||||
During any code integration, you may get reports that some files have
|
||||
conflicting changes. If you need help resolving them, don't be afraid to ask
|
||||
around! After you've resolved them, mark them as done with `git add` command.
|
||||
You may then continue with `git {{action}} --continue`.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Memgraph Code Documentation
|
||||
|
||||
IMPORTANT: auto-generated (run doxygen Doxyfile in the project root)
|
||||
IMPORTANT: Auto-generated (run doxygen Doxyfile in the project root).
|
||||
|
||||
* HTML - just open docs/doxygen/html/index.html
|
||||
|
||||
* Latex - run make inside docs/doxygen/latex
|
||||
* HTML - Open docs/doxygen/html/index.html.
|
||||
* Latex - Run make inside docs/doxygen/latex.
|
||||
|
||||
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
@@ -1,20 +0,0 @@
|
||||
## Dynamic Graph Partitioner
|
||||
|
||||
Memgraph supports dynamic graph partitioning which dynamically improves
|
||||
performance on badly partitioned dataset over workers. To enable it, the user
|
||||
should use the following flag when firing up the *master* node:
|
||||
|
||||
```plaintext
|
||||
--dynamic_graph_partitioner_enable
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | Default Value | Description | Range |
|
||||
|------|---------------|-------------|-------|
|
||||
|--dgp_improvement_threshold | 10 | How much better should specific node score
|
||||
be to consider a migration to another worker. This represents the minimal
|
||||
difference between new score that the vertex will have when migrated and the
|
||||
old one such that it's migrated. | Min: 1, Max: 100
|
||||
|--dgp_max_batch_size | 2000 | Maximal amount of vertices which should be
|
||||
migrated in one dynamic graph partitioner step. | Min: 1, Max: MaxInt32 |
|
||||
3
docs/feature_spec/contents.md
Normal file
3
docs/feature_spec/contents.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Feature Specifications
|
||||
|
||||
* [Python Query Modules](python-query-modules.md)
|
||||
@@ -1,214 +1,275 @@
|
||||
# High Availability (abbr. HA)
|
||||
|
||||
## Introduction
|
||||
## High Level Context
|
||||
|
||||
High availability is a characteristic of a system which aims to ensure a
|
||||
certain level of operational performance for a higher-than-normal period.
|
||||
Although there are multiple ways to design highly available systems, Memgraph
|
||||
strives to achieve HA by elimination of single points of failure. In essence,
|
||||
this implies adding redundancy to the system so that a failure of a component
|
||||
does not imply the failure of the entire system.
|
||||
does not imply the failure of the entire system. To ensure this, HA Memgraph
|
||||
implements the [Raft consensus algorithm](https://raft.github.io/).
|
||||
|
||||
## Theoretical Background
|
||||
Correct implementation of the algorithm guarantees that the cluster will be
|
||||
fully functional (available) as long as any strong majority of the servers are
|
||||
operational and can communicate with each other and with clients. For example,
|
||||
clusters of three or four machines can tolerate the failure of a single server,
|
||||
clusters of five and six machines can tolerate the failure of any two servers,
|
||||
and so on. Therefore, we strongly recommend a setup of an odd-sized cluster.
|
||||
|
||||
The following chapter serves as an introduction into some theoretical aspects
|
||||
of Memgraph's high availability implementation. If the reader is solely
|
||||
interested in design decisions around HA implementation, they can skip this
|
||||
chapter.
|
||||
### Performance Implications
|
||||
|
||||
An important implication of any HA implementation stems from Eric Brewer's
|
||||
[CAP Theorem](https://fenix.tecnico.ulisboa.pt/downloadFile/1126518382178117/10.e-CAP-3.pdf)
|
||||
which states that it is impossible for a distributed system to simultaneously
|
||||
achieve:
|
||||
Internally, Raft achieves high availability by keeping a consistent replicated
|
||||
log on each server within the cluster. Therefore, we must successfully replicate
|
||||
a transaction on the majority of servers within the cluster before we actually
|
||||
commit it and report the result back to the client. This operation represents
|
||||
a significant performance hit when compared with single node version of
|
||||
Memgraph.
|
||||
|
||||
* Consistency (C - every read receives the most recent write or an error)
|
||||
* Availability (A - every request receives a response that is not an error)
|
||||
* Partition tolerance (P - The system continues to operate despite an
|
||||
arbitrary number of messages being dropped by the
|
||||
network between nodes)
|
||||
Luckily, the algorithm can be tweaked in a way which allows read-only
|
||||
transactions to perform significantly better than those which modify the
|
||||
database state. That being said, the performance of read-only operations
|
||||
is still not going to be on par with single node Memgraph.
|
||||
|
||||
In the context of HA, Memgraph should strive to achieve CA.
|
||||
This section will be updated with exact numbers once we integrate HA with
|
||||
new storage.
|
||||
|
||||
### Consensus
|
||||
With the old storage, write throughput was almost five times lower than read
|
||||
throughput (~30000 reads per second vs ~6000 writes per second).
|
||||
|
||||
Implications of the CAP theorem naturally lead us towards introducing a
|
||||
cluster of machines which will have identical internal states. When a designated
|
||||
machine for handling client requests fails, it can simply be replaced with
|
||||
another.
|
||||
## User Facing Setup
|
||||
|
||||
Well... turns out this is not as easy as it sounds :(
|
||||
### How to Setup HA Memgraph Cluster?
|
||||
|
||||
Keeping around a cluster of machines with consistent internal state is an
|
||||
inherently difficult problem. More precisely, this problem is as hard as
|
||||
getting a cluster of machines to agree on a single value, which is a highly
|
||||
researched area in distributed systems. Our research of state of the art
|
||||
consensus algorithms lead us to Diego Ongaro's
|
||||
[Raft algorithm](https://raft.github.io/raft.pdf).
|
||||
First, the user needs to install `memgraph_ha` package on each machine
|
||||
in their cluster. HA Memgraph should be available as a Debian package,
|
||||
so its installation on each machine should be as simple as:
|
||||
|
||||
#### Raft
|
||||
```plaintext
|
||||
dpkg -i /path/to/memgraph_ha_<version>.deb
|
||||
```
|
||||
|
||||
As you might have guessed, analyzing each subtle detail of Raft goes way
|
||||
beyond the scope of this document. In the remainder of the chapter we will
|
||||
outline only the most important ideas and implications, leaving all further
|
||||
analysis to the reader. Detailed explanation can be found either in Diego's
|
||||
[dissertation](https://ramcloud.stanford.edu/~ongaro/thesis.pdf) \[1\] or the
|
||||
Raft [paper](https://raft.github.io/raft.pdf) \[2\].
|
||||
After successful installation of the `memgraph_ha` package, the user should
|
||||
finish its configuration before attempting to start the cluster.
|
||||
|
||||
In essence, Raft allows us to implement the previously mentioned idea of
|
||||
managing a cluster of machines with identical internal states. In other
|
||||
words, the Raft protocol allows us to manage a cluster of replicated
|
||||
state machines which is fully functional as long as the *majority* of
|
||||
the machines in the cluster operate correctly.
|
||||
There are two main things that need to be configured on every node in order for
|
||||
the cluster to be able to run:
|
||||
|
||||
Another important fact is that those state machines must be *deterministic*.
|
||||
In other words, the same command on two different machines with the same
|
||||
internal state must yield the same result. This is important because Memgraph,
|
||||
as a black box, is not entirely deterministic. Non-determinism can easily be
|
||||
introduced by the user (e.g. by using the `rand` function) or by algorithms
|
||||
behind query execution (e.g. introducing fuzzy logic in the planner could yield
|
||||
a different order of results). Luckily, once we enter the storage level,
|
||||
everything should be fully deterministic.
|
||||
1. The user has to edit the main configuration file and specify the unique node
|
||||
ID to each server in the cluster
|
||||
2. The user has to create a file that describes all IP addresses of all servers
|
||||
that will be used in the cluster
|
||||
|
||||
To summarize, Raft is a protocol which achieves consensus in a cluster of
|
||||
deterministic state machines via log replication. The cluster is fully
|
||||
functional if the majority of the machines work correctly. The reader
|
||||
is strongly encouraged to gain a deeper understanding (at least read through
|
||||
the paper) of Raft before reading the rest of this document.
|
||||
The `memgraph_ha` binary loads all main configuration parameters from
|
||||
`/etc/memgraph/memgraph_ha.conf`. On each node of the cluster, the user should
|
||||
uncomment the `--server-id=0` parameter and change its value to the `server_id`
|
||||
of that node.
|
||||
|
||||
The last step before starting the server is to create a `coordination`
|
||||
configuration file. That file is already present as an example in
|
||||
`/etc/memgraph/coordination.json.example` and you have to copy it to
|
||||
`/etc/memgraph/coordination.json` and edit it according to your cluster
|
||||
configuration. The file contains coordination info consisting of a list of
|
||||
`server_id`, `ip_address` and `rpc_port` lists. The assumed contents of the
|
||||
`coordination.json` file are:
|
||||
|
||||
```plaintext
|
||||
[
|
||||
[1, "192.168.0.1", 10000],
|
||||
[2, "192.168.0.2", 10000],
|
||||
[3, "192.168.0.3", 10000]
|
||||
]
|
||||
```
|
||||
Here, each line corresponds to coordination of one server. The first entry is
|
||||
that server's ID, the second is its IP address and the third is the RPC port it
|
||||
listens to. This port should not be confused with the port used for client
|
||||
interaction via the Bolt protocol.
|
||||
|
||||
The `ip_address` entered for each `server_id` *must* match the exact IP address
|
||||
that belongs to that server and that will be used to communicate to other nodes
|
||||
in the cluster. The coordination configuration file *must* be identical on all
|
||||
nodes in the cluster.
|
||||
|
||||
After the user has set the `server_id` on each node in
|
||||
`/etc/memgraph/memgraph_ha.conf` and provided the same
|
||||
`/etc/memgraph/coordination.json` file to each node in the cluster, they can
|
||||
start the Memgraph HA service by issuing the following command on each node in
|
||||
the cluster:
|
||||
|
||||
```plaintext
|
||||
systemctl start memgraph_ha
|
||||
```
|
||||
|
||||
### How to Configure Raft Parameters?
|
||||
|
||||
All Raft configuration parameters can be controlled by modifying
|
||||
`/etc/memgraph/raft.json`. The assumed contents of the `raft.json` file are:
|
||||
|
||||
```plaintext
|
||||
{
|
||||
"election_timeout_min": 750,
|
||||
"election_timeout_max": 1000,
|
||||
"heartbeat_interval": 100,
|
||||
"replication_timeout": 20000,
|
||||
"log_size_snapshot_threshold": 50000
|
||||
}
|
||||
```
|
||||
|
||||
The meaning behind each entry is demystified in the following table:
|
||||
|
||||
Flag | Description
|
||||
------------------------------|------------
|
||||
`election_timeout_min` | Lower bound for the randomly sampled reelection timer given in milliseconds
|
||||
`election_timeout_max` | Upper bound for the randomly sampled reelection timer given in milliseconds
|
||||
`heartbeat_interval` | Time interval between consecutive heartbeats given in milliseconds
|
||||
`replication_timeout` | Time interval allowed for data replication given in milliseconds
|
||||
`log_size_snapshot_threshold` | Allowed number of entries in Raft log before its compaction
|
||||
|
||||
### How to Query HA Memgraph via Proxy?
|
||||
|
||||
This chapter describes how to query HA Memgraph using our proxy server.
|
||||
Note that this is not intended to be a long-term solution. Instead, we will
|
||||
implement a proper Memgraph HA client which is capable of communicating with
|
||||
the HA cluster. Once our own client is implemented, it will no longer be
|
||||
possible to query HA Memgraph using other clients (such as neo4j client).
|
||||
|
||||
The Bolt protocol that is exposed by each Memgraph HA node is an extended
|
||||
version of the standard Bolt protocol. In order to be able to communicate with
|
||||
the highly available cluster of Memgraph HA nodes, the client must have some
|
||||
logic implemented in itself so that it can communicate correctly with all nodes
|
||||
in the cluster. To facilitate a faster start with the HA cluster we will build
|
||||
the Memgraph HA proxy binary that communicates with all nodes in the HA cluster
|
||||
using the extended Bolt protocol and itself exposes a standard Bolt protocol to
|
||||
the user. All standard Bolt clients (libraries and custom systems) can
|
||||
communicate with the Memgraph HA proxy without any code modifications.
|
||||
|
||||
The HA proxy should be deployed on each client machine that is used to
|
||||
communicate with the cluster. It can't be deployed on the Memgraph HA nodes!
|
||||
|
||||
When using the Memgraph HA proxy, the communication flow is described in the
|
||||
following diagram:
|
||||
|
||||
```plaintext
|
||||
Memgraph HA node 1 -----+
|
||||
|
|
||||
Memgraph HA node 2 -----+ Memgraph HA proxy <---> any standard Bolt client (C, Java, PHP, Python, etc.)
|
||||
|
|
||||
Memgraph HA node 3 -----+
|
||||
```
|
||||
|
||||
To setup the Memgraph HA proxy the user should install the `memgraph_ha_proxy`
|
||||
package.
|
||||
|
||||
After its successful installation, the user should enter all endpoints of the
|
||||
HA Memgraph cluster servers into the configuration before attempting to start
|
||||
the HA Memgraph proxy server.
|
||||
|
||||
The HA Memgraph proxy server loads all of its configuration from
|
||||
`/etc/memgraph/memgraph_ha_proxy.conf`. Assuming that the cluster is set up
|
||||
like in the previous examples, the user should uncomment and enter the following
|
||||
value into the `--endpoints` parameter:
|
||||
|
||||
```plaintext
|
||||
--endpoints=192.168.0.1:7687,192.168.0.2:7687,192.168.0.3:7687
|
||||
```
|
||||
|
||||
Note that the IP addresses used in the example match the individual cluster
|
||||
nodes IP addresses, but the ports used are the Bolt server ports exposed by
|
||||
each node (currently the default value of `7687`).
|
||||
|
||||
The user can now start the proxy by using the following command:
|
||||
|
||||
```plaintext
|
||||
systemctl start memgraph_ha_proxy
|
||||
```
|
||||
|
||||
After the proxy has been started, the user can query the HA cluster by
|
||||
connecting to the HA Memgraph proxy IP address using their favorite Bolt
|
||||
client.
|
||||
|
||||
## Integration with Memgraph
|
||||
|
||||
The first thing that should be defined is a single instruction within the
|
||||
context of Raft (i.e. a single entry in a replicated log). As mentioned
|
||||
before, these instructions should be completely deterministic when applied
|
||||
context of Raft (i.e. a single entry in a replicated log).
|
||||
These instructions should be completely deterministic when applied
|
||||
to the state machine. We have therefore decided that the appropriate level
|
||||
of abstraction within Memgraph corresponds to `StateDelta`-s (data structures
|
||||
of abstraction within Memgraph corresponds to `Delta`s (data structures
|
||||
which describe a single change to the Memgraph state, used for durability
|
||||
in WAL). Moreover, a single instruction in a replicated log will consist of a
|
||||
batch of `StateDelta`s which correspond to a single **committed** transaction.
|
||||
This decision both improves performance and handles some special cases that
|
||||
present themselves otherwise by leveraging the knowledge that the transaction
|
||||
should be committed.
|
||||
batch of `Delta`s which correspond to a single transaction that's about
|
||||
to be **committed**.
|
||||
|
||||
"What happens with aborted transactions?"
|
||||
Apart from `Delta`s, there are certain operations within the storage called
|
||||
`StorageGlobalOperations` which do not conform to usual transactional workflow
|
||||
(e.g. Creating indices). Since our storage engine implementation guarantees
|
||||
that at the moment of their execution no other transactions are active, we can
|
||||
safely replicate them as well. In other words, no additional logic needs to be
|
||||
implemented because of them.
|
||||
|
||||
A great question, they are handled solely by the leader which is the only
|
||||
machine that communicates with the client. Aborted transactions do not alter
|
||||
the state of the database and there is no need to replicate it to other machines
|
||||
in the cluster. If, for instance, the leader dies before returning the result
|
||||
of some read operation in an aborted transaction, the client will notice that
|
||||
the leader has crashed. A new leader will be elected in the next term and the
|
||||
client should retry the transaction.
|
||||
Therefore, we will introduce a new `RaftDelta` object which can be constructed
|
||||
both from storage `Delta` and `StorageGlobalOperation`. Instead of appending
|
||||
these to WAL (as we do in single node), we will start to replicate them across
|
||||
our cluster. Once we have replicated the corresponding Raft log entry on
|
||||
majority of the cluster, we are able to safely commit the transaction or execute
|
||||
a global operation. If for any reason the replication fails (leadership change,
|
||||
worker failures, etc.) the transaction will be aborted.
|
||||
|
||||
"OK, that makes sense! But, wait a minute, this is broken by design! Merely
|
||||
generating `StateDelta`s on the leader for any transaction will taint its
|
||||
internal storage before sending the first RPC to some follower. This deviates
|
||||
from Raft and will crash the universe!"
|
||||
In the follower mode, we need to be able to apply `RaftDelta`s we got from
|
||||
the leader when the protocol allows us to do so. In that case, we will use the
|
||||
same concepts from durability in storage v2, i.e., applying deltas maps
|
||||
completely to recovery from WAL in storage v2.
|
||||
|
||||
Another great observation. It is indeed true that applying `StateDelta`s makes
|
||||
changes to local storage, but only a single type of `StateDelta` makes that
|
||||
change durable. That `StateDelta` type is called `TRANSACTION_COMMIT` and we
|
||||
will change its behaviour when working as a HA instance. More precisely, we
|
||||
must not allow the transaction engine to modify the commit log saying that
|
||||
the transaction has been committed. That action should be delayed until those
|
||||
`StateDelta`s have been applied to the majority of the cluster. At that point
|
||||
the commit log can be safely modified leaving it up to Raft to ensure the
|
||||
durability of the transaction.
|
||||
## Test and Benchmark Strategy
|
||||
|
||||
We should also address one subtle detail that arises in this case. Consider
|
||||
the following scenario:
|
||||
We have already implemented some integration and stress tests. These are:
|
||||
|
||||
* The leader starts working on a transaction which creates a new record in the
|
||||
database. Suppose that record is stored in the leader's internal storage
|
||||
but the transaction was not committed (i.e. no such entry in the commit log).
|
||||
* The leader should start replicating those `StateDelta`s to its followers
|
||||
but, suddenly, it's cut off from the rest of the cluster.
|
||||
* Due to timeout, a new election is held and a new leader has been elected.
|
||||
* Our old leader comes back to life and becomes a follower.
|
||||
* The new leader receives a transaction which creates that same record, but
|
||||
this transaction is successfully replicated and committed by the new leader.
|
||||
1. leader election -- Tests whether leader election works properly.
|
||||
2. basic test -- Tests basic leader election and log replication.
|
||||
3. term updates test -- Tests a specific corner case (which used to fail)
|
||||
regarding term updates.
|
||||
4. log compaction test -- Tests whether log compaction works properly.
|
||||
5. large log entries -- Tests whether we can successfully replicate relatively
|
||||
large log entries.
|
||||
6. index test -- Tests whether index creation works in HA.
|
||||
7. normal operation stress test -- Long running concurrent stress test under
|
||||
normal conditions (no failures).
|
||||
8. read benchmark -- Measures read throughput in HA.
|
||||
9. write benchmark -- Measures write throughput in HA.
|
||||
|
||||
The problem lies in the fact that there is still a record within the internal
|
||||
storage of our old leader with the same transaction ID and GID as the recently
|
||||
committed record by the new leader. Obviously, this is broken. As a solution, on
|
||||
each transition from `Leader` to `Follower`, we will reinitialize storage, reset
|
||||
the transaction engine and recover data from the Raft log. This will ensure all
|
||||
ongoing transactions which have "polluted" the storage will be gone.
|
||||
At the moment, our main goal is to pass existing tests and have a stable version
|
||||
on our stress test. We should also implement a stress test which occasionally
|
||||
introduces different types of failures in our cluster (we did this kind of
|
||||
testing manually thus far). Passing these tests should convince us that we have
|
||||
a "stable enough" version which we can start pushing to our customers.
|
||||
|
||||
"When will followers append that transaction to their commit logs?"
|
||||
Additional (proper) testing should probably involve some ideas from
|
||||
[here](https://jepsen.io/analyses/dgraph-1-0-2)
|
||||
|
||||
When the leader deduces that the transaction is safe to commit, it will include
|
||||
the relevant information in all further heartbeats which will alert the
|
||||
followers that it is safe to commit those entries from their raft logs.
|
||||
Naturally, the followers need not to delay appending data to the commit log
|
||||
as they know that the transaction has already been committed (from the clusters
|
||||
point of view). If this sounds really messed up, seriously, read the Raft paper.
|
||||
## Possible Future Changes/Improvements/Extensions
|
||||
|
||||
"How does the raft log differ from WAL"
|
||||
There are two general directions in which we can alter HA Memgraph. The first
|
||||
direction assumes we are going to stick with the Raft protocol. In that case
|
||||
there are a few known ways to extend the basic algorithm in order to gain
|
||||
better performance or achieve extra functionality. In no particular order,
|
||||
these are:
|
||||
|
||||
Conceptually, it doesn't. When operating in HA, we don't really need the
|
||||
recovery mechanisms implemented in Memgraph thus far. When a dead machine
|
||||
comes back to life, it will eventually come in sync with the rest of the
|
||||
cluster and everything will be done using the machine's raft log as well
|
||||
as the messages received from the cluster leader.
|
||||
1. Improving read performance using leader leases [Section 6.4 from Raft thesis]
|
||||
2. Introducing cluster membership changes [Chapter 4 from Raft thesis]
|
||||
3. Introducing a [learner mode](https://etcd.io/docs/v3.3.12/learning/learner/).
|
||||
4. Consider different log compaction strategies [Chapter 5 from Raft thesis]
|
||||
5. Removing HA proxy and implementing our own HA Memgraph client.
|
||||
|
||||
"Those logs will become huge, isn't that recovery going to be painfully slow?"
|
||||
On the other hand, we might decide in the future to base our HA implementation
|
||||
on a completely different protocol which might even offer different guarantees.
|
||||
In that case we probably need to do a bit more of market research and weigh the
|
||||
trade-offs of different solutions.
|
||||
[This](https://www.postgresql.org/docs/9.5/different-replication-solutions.html)
|
||||
might be a good starting point.
|
||||
|
||||
True, but there are mechanisms for making raft logs more compact. The most
|
||||
popular method is, wait for it, making snapshots :)
|
||||
Although the process of bringing an old machine back to life is a long one,
|
||||
it doesn't really affect the performance of the cluster in a great degree.
|
||||
The cluster will work perfectly fine with that machine being way out of sync.
|
||||
## Reading materials
|
||||
|
||||
"I don't know, everything seems to be a lot slower than before!"
|
||||
|
||||
Absolutely true, the user should be aware that they will suffer dire
|
||||
consequences on the performance side if they choose to be highly available.
|
||||
As Frankie says, "That's life!".
|
||||
|
||||
"Also, I didn't really care about most of the things you've said. I'm
|
||||
not a part of the storage team and couldn't care less about the issues you
|
||||
face, how does HA affect 'my part of the codebase'?"
|
||||
|
||||
Answer for query execution: That's ok, you'll be able to use the same beloved
|
||||
API (when we implement it, he he :) towards storage and continue to
|
||||
make fun of us when you find a bug.
|
||||
|
||||
Answer for infrastructure: We'll talk. Some changes will surely need to
|
||||
be made on the Memgraph client. There is a chapter in Diego's dissertation
|
||||
called 'Client interaction', but we'll cross that bridge when we get there.
|
||||
There will also be the whole 'integration with Jepsen tests' thing going on.
|
||||
|
||||
Answer for analytics: I'm astonished you've read this article. Wanna join
|
||||
storage?
|
||||
|
||||
### Subtlety Regarding Reads
|
||||
|
||||
As we have hinted in the previous chapter, we would like to bypass log
|
||||
replication for operations which do not alter the internal state of Memgraph.
|
||||
Those operations should therefore be handled only by the leader, which is not
|
||||
as trivial as it seems. The subtlety arises from the fact that a (newly-elected)
|
||||
leader can have an entry in its log which was committed by the previous leader
|
||||
that has crashed but that entry is not yet committed in its internal storage
|
||||
by the current leader. Moreover, the rule about safely committing logs that are
|
||||
replicated on the majority of the cluster only applies for entries replicated in
|
||||
the leaders current term. Therefore, we are faced with two issues:
|
||||
|
||||
* We cannot simply perform read operations if the leader has a non-committed
|
||||
entry in its log (breaks consistency).
|
||||
* Replicating those entries onto the majority of the cluster is not enough
|
||||
to guarantee that they can be safely committed.
|
||||
|
||||
This can be solved by introducing a blank no-op operation which the new leader
|
||||
will try to replicate at the start of its term. Once that operation is
|
||||
replicated and committed, the leader can safely perform those non-altering
|
||||
operations on its own.
|
||||
|
||||
For further information about these issues, you should check out section
|
||||
5.4.2 from the raft paper \[1\] which hints as to why its not safe to commit
|
||||
entries from previous terms. Also, you should check out section 6.4 from
|
||||
the thesis \[2\] which goes into more details around efficiently processing
|
||||
read-only queries.
|
||||
|
||||
## How do we test HA
|
||||
|
||||
[Check this out](https://jepsen.io/analyses/dgraph-1-0-2)
|
||||
1. [Raft paper](https://raft.github.io/raft.pdf)
|
||||
2. [Raft thesis](https://github.com/ongardie/dissertation) (book.pdf)
|
||||
3. [Raft playground](https://raft.github.io/)
|
||||
4. [Leader Leases](https://blog.yugabyte.com/low-latency-reads-in-geo-distributed-sql-with-raft-leader-leases/)
|
||||
5. [Improving Raft ETH](https://pub.tik.ee.ethz.ch/students/2017-FS/SA-2017-80.pdf)
|
||||
|
||||
185
docs/feature_spec/python-query-modules.md
Normal file
185
docs/feature_spec/python-query-modules.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# Python 3 Query Modules
|
||||
|
||||
## Introduction
|
||||
|
||||
Memgraph exposes a C API for writing the so called Query Modules. These
|
||||
modules contain definitions of procedures which can be invoked through the
|
||||
query language using the `CALL ... YIELD ...` syntax. This mechanism allows
|
||||
database users to extend Memgraph with their own algorithms and
|
||||
functionalities.
|
||||
|
||||
Using a low level language like C can be quite cumbersome for writing modules,
|
||||
so it seems natural to add support for a higher level language on top of the
|
||||
existing C API.
|
||||
|
||||
There are languages written exactly for this purpose of extending C with high
|
||||
level constructs, for example Lua and Guile. Instead of those, we have chosen
|
||||
Python 3 to be the first high level language we will support. The primary reason
|
||||
being that it's very popular, so more people should be able to write modules.
|
||||
Another benefit of Python which comes out of its popularity is the large
|
||||
ecosystem of libraries, especially graph algorithm related ones like NetworkX.
|
||||
Python does have significant performance and implementation downsides compared
|
||||
to Lua and Guile, but these are described in more detail later in this
|
||||
document.
|
||||
|
||||
## Python 3 API Overview
|
||||
|
||||
The Python 3 API should be as user friendly as possible as well as look
|
||||
Pythonic. This implies that some functions from the C API will not map to the
|
||||
exact same functions. The most obvious case for a Pythonic approach is
|
||||
registering procedures of a query module. Let's take a look at the C example
|
||||
and its transformation to Python.
|
||||
|
||||
```c
|
||||
static void procedure(const struct mgp_list *args,
|
||||
const struct mgp_graph *graph, struct mgp_result *result,
|
||||
struct mgp_memory *memory);
|
||||
|
||||
int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) {
|
||||
struct mgp_proc *proc =
|
||||
mgp_module_add_read_procedure(module, "procedure", procedure);
|
||||
if (!proc) return 1;
|
||||
if (!mgp_proc_add_arg(proc, "required_arg",
|
||||
mgp_type_nullable(mgp_type_any())))
|
||||
return 1;
|
||||
struct mgp_value *null_value = mgp_value_make_null(memory);
|
||||
if (!mgp_proc_add_opt_arg(proc, "optional_arg",
|
||||
mgp_type_nullable(mgp_type_any()), null_value)) {
|
||||
mgp_value_destroy(null_value);
|
||||
return 1;
|
||||
}
|
||||
mgp_value_destroy(null_value);
|
||||
if (!mgp_proc_add_result(proc, "result", mgp_type_string())) return 1;
|
||||
if (!mgp_proc_add_result(proc, "args",
|
||||
mgp_type_list(mgp_type_nullable(mgp_type_any()))))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
In Python things should be a lot simpler.
|
||||
|
||||
```Python
|
||||
# mgp.read_proc obtains the procedure name via __name__ attribute of a function.
|
||||
@mgp.read_proc(# Arguments passed to multiple mgp_proc_add_arg calls
|
||||
(('required_arg', mgp.Nullable(mgp.Any)), ('optional_arg', mgp.Nullable(mgp.Any), None)),
|
||||
# Result fields passed to multiple mgp_proc_add_result calls
|
||||
(('result', str), ('args', mgp.List(mgp.Nullable(mgp.Any)))))
|
||||
def procedure(args, graph, result, memory):
|
||||
pass
|
||||
```
|
||||
|
||||
Here we have replaced `mgp_module_*` and `mgp_proc_*` C API with a much
|
||||
simpler decorator function in Python -- `mgp.read_proc`. The types of
|
||||
arguments and result fields can both be our types as well as Python builtin
|
||||
types which can map to supported `mgp_value` types. The expected builtin types
|
||||
we ought to support are: `bool`, `str`, `int`, `float` and `map`. While the
|
||||
rest of the types are provided via our Python API. Optionally, we can add
|
||||
convenience support for `object` type which would map to
|
||||
`mgp.Nullable(mgp.Any)` and `list` which would map to
|
||||
`mgp.List(mgp.Nullable(mgp.Any))`. Also, it makes sense to take a look if we
|
||||
can leverage Python's `typing` module here.
|
||||
|
||||
Another Pythonic change is to remove `mgp_value` C API from Python altogether.
|
||||
This means that the arguments a Python procedure receives are not `mgp_value`
|
||||
instances but rather `PyObject` instances. In other words, our implementation
|
||||
would immediately marshal `mgp_value` to corresponding type in Python.
|
||||
Obviously we would need to provide our own Python types for non-builtin
|
||||
things like `mgp.Vertex` (equivalent to `mgp_vertex`) and other.
|
||||
|
||||
Continuing from our example above, let's say the procedure was invoked through
|
||||
Cypher using the following query.
|
||||
|
||||
MATCH (n) CALL py_module.procedure(42, n) YIELD *;
|
||||
|
||||
The Python procedure could then do the following and complete without throwing
|
||||
neither the AssertionError nor the ValueError.
|
||||
|
||||
```Python
|
||||
def procedure(args, graph, result, memory):
|
||||
assert isinstance(args, list)
|
||||
# Unpacking throws ValueError if args does not contain exactly 2 values.
|
||||
required_arg, optional_arg = args
|
||||
assert isintance(required_arg, int)
|
||||
assert isinstance(optional_arg, mgp.Vertex)
|
||||
```
|
||||
|
||||
The rest of the C API should naturally map to either top level functions or
|
||||
class methods as appropriate.
|
||||
|
||||
## Loading Python Query Modules
|
||||
|
||||
Our current mechanism for loading the modules is to look for `.so` files in
|
||||
the directory specified by `--query-modules` flag. This is done when Memgraph
|
||||
is started. We can extend this mechanism to look for `.py` files in addition
|
||||
to `.so` files in the same directory and import them in the embedded Python
|
||||
interpreter. The only issue is embedding the interpreter in Memgraph. There
|
||||
are multiple choices:
|
||||
|
||||
1. Building Memgraph and statically linking to Python.
|
||||
2. Building Memgraph and dynamically linking to Python, and distributing
|
||||
Python with Memgraph's installation.
|
||||
3. Building Memgraph and dynamically linking to Python, but without
|
||||
distributing the Python library.
|
||||
4. Building Memgraph and optionally loading Python library by trying to
|
||||
`dlopen` it.
|
||||
|
||||
The first two options are only viable if the Python license allows, and this
|
||||
will need further investigation.
|
||||
|
||||
The third option adds Python as an installation dependency for Memgraph, and
|
||||
without it Memgraph will not run. This is problematic for users which cannot
|
||||
or do not want to install Python 3.
|
||||
|
||||
The fourth option avoids all of the issues present in the first 3 options, but
|
||||
comes at a higher implementation cost. We would need to try to `dlopen` the
|
||||
Python library and setup function pointers. If we succeed we would import
|
||||
`.py` files from the `--query-modules` directory. On the other hand, if the
|
||||
user does not have Python, `dlopen` would fail and Memgraph would run without
|
||||
Python support.
|
||||
|
||||
After live discussion, we've decided to go with option 3. This way we don't
|
||||
have to worry about mismatching Python versions we support and what the users
|
||||
expect. Also, we should target Python 3.5 as that should be common between
|
||||
Debian and CentOS for which we ship installation packages.
|
||||
|
||||
## Performance and Implementation Problems
|
||||
|
||||
As previously mentioned, embedding Python introduces usability issues compared
|
||||
to other embeddable languages.
|
||||
|
||||
The first, major issue is Global Interpreter Lock (GIL). Initializing Python
|
||||
will start a single global interpreter and running multiple threads will
|
||||
require acquiring GIL. In practice, this means that when multiple users run a
|
||||
procedure written in Python in parallel the execution will not actually be
|
||||
parallel. Python's interpreter will jump between executing one user's
|
||||
procedure and the other's. This can be quite an issue for long running
|
||||
procedures when multiple users are querying Memgraph. The solution for this
|
||||
issue is Python's API for sub-interpreters. Unfortunately, the support for
|
||||
them is rather poor and the API contains a lot of critical bugs when we tried
|
||||
to use them. For the time being, we will have to accept GIL and its downsides.
|
||||
Perhaps in the future we will gain more knowledge on how we could reduce the
|
||||
acquire rate of GIL or the sub-interpreter API will get improved.
|
||||
|
||||
Another major issue is memory allocation. Python's C API does not have support
|
||||
for setting up a temporary allocator during execution of a single function.
|
||||
It only has support for setting up a global heap allocator. This obviously
|
||||
impacts our control of memory during a query procedure invocation. Besides
|
||||
potential performance penalty, a procedure could allocate much more memory
|
||||
than we would actually allow for execution of a single query. This means that
|
||||
options controlling the memory limit during query execution are useless. On
|
||||
the bright side, Python does use block style allocators and reference
|
||||
counting, so the performance penalty and global memory usage should not be
|
||||
that terrible.
|
||||
|
||||
The final issue that isn't as major as the ones above is the global state of
|
||||
the interpreter. In practice this means that any registered procedure and
|
||||
imported module has access to any other procedure and module. This may pollute
|
||||
the namespace for other users, but it should not be much of a problem because
|
||||
Python always has things under a module scope. The other, slightly bigger
|
||||
downside is that a malicious user could use this knowledge to modify other
|
||||
modules and procedures. This seems like a major issue, but if we take the
|
||||
bigger picture into consideration, we already have a security issue in general
|
||||
by invoking `dlopen` on `.so` and potentially running arbitrary code. This was
|
||||
the trade off we chose to allow users to extend Memgraph. It's up to the users
|
||||
to write sane extensions and protect their servers from access.
|
||||
202
docs/feature_spec/replication.md
Normal file
202
docs/feature_spec/replication.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# Replication
|
||||
|
||||
## High Level Context
|
||||
|
||||
Replication is a method that ensures that multiple database instances are
|
||||
storing the same data. To enable replication, there must be at least two
|
||||
instances of Memgraph in a cluster. Each instance has one of either two roles:
|
||||
main or replica. The main instance is the instance that accepts writes to the
|
||||
database and replicates its state to the replicas. In a cluster, there can only
|
||||
be one main. There can be one or more replicas. None of the replicas will accept
|
||||
write queries, but they will always accept read queries (there is an exception
|
||||
to this rule and is described below). Replicas can also be configured to be
|
||||
replicas of replicas, not necessarily replicas of the main. Each instance will
|
||||
always be reachable using the standard supported communication protocols. The
|
||||
replication will replicate WAL data. All data is transported through a custom
|
||||
binary protocol that will try remain backward compatible, so that replication
|
||||
immediately allows for zero downtime upgrades.
|
||||
|
||||
Each replica can be configured to accept replicated data in one of the following
|
||||
modes:
|
||||
- synchronous
|
||||
- asynchronous
|
||||
- semi-synchronous
|
||||
|
||||
### Synchronous Replication
|
||||
|
||||
When the data is replicated to a replica synchronously, all of the data of a
|
||||
currently pending transaction must be sent to the synchronous replica before the
|
||||
transaction is able to commit its changes.
|
||||
|
||||
This mode has a positive implication that all data that is committed to the
|
||||
main will always be replicated to the synchronous replica. It also has a
|
||||
negative performance implication because non-responsive replicas could grind all
|
||||
query execution to a halt.
|
||||
|
||||
This mode is good when you absolutely need to be sure that all data is always
|
||||
consistent between the main and the replica.
|
||||
|
||||
### Asynchronous Replication
|
||||
|
||||
When the data is replicated to a replica asynchronously, all pending
|
||||
transactions are immediately committed and their data is replicated to the
|
||||
asynchronous replica in the background.
|
||||
|
||||
This mode has a positive performance implication in which it won't slow down
|
||||
query execution. It also has a negative implication that the data between the
|
||||
main and the replica is almost never in a consistent state (when the data is
|
||||
being changed).
|
||||
|
||||
This mode is good when you don't care about consistency and only need an
|
||||
eventually consistent cluster, but you care about performance.
|
||||
|
||||
### Semi-synchronous Replication
|
||||
|
||||
When the data is replicated to a replica semi-synchronously, the data is
|
||||
replicated using both the synchronous and asynchronous methodology. The data is
|
||||
always replicated synchronously, but, if the replica for any reason doesn't
|
||||
respond within a preset timeout, the pending transaction is committed and the
|
||||
data is replicated to the replica asynchronously.
|
||||
|
||||
This mode has a positive implication that all data that is committed is
|
||||
*mostly* replicated to the semi-synchronous replica. It also has a negative
|
||||
performance implication as the synchronous replication mode.
|
||||
|
||||
This mode is useful when you want the replication to be synchronous to ensure
|
||||
that the data within the cluster is consistent, but you don't want the main
|
||||
to grind to a halt when you have a non-responsive replica.
|
||||
|
||||
### Addition of a New Replica
|
||||
|
||||
Each replica, when added to the cluster (in any mode), will first start out as
|
||||
an asynchronous replica. That will allow replicas that have fallen behind to
|
||||
first catch-up to the current state of the database. When the replica is in a
|
||||
state that it isn't lagging behind the main it will then be promoted (in a brief
|
||||
stop-the-world operation) to a semi-synchronous or synchronous replica. Slaves
|
||||
that are added as asynchronous replicas will remain asynchronous.
|
||||
|
||||
## User Facing Setup
|
||||
|
||||
### How to Setup a Memgraph Cluster with Replication?
|
||||
|
||||
Replication configuration is done primarily through openCypher commands. This
|
||||
allows the cluster to be dynamically rearranged (new leader election, addition
|
||||
of a new replica, etc.).
|
||||
|
||||
Each Memgraph instance when first started will be a main. You have to change
|
||||
the role of all replica nodes using the following openCypher query before you
|
||||
can enable replication on the main:
|
||||
|
||||
```plaintext
|
||||
SET REPLICATION ROLE TO (MAIN|REPLICA) WITH PORT <port_number>;
|
||||
```
|
||||
|
||||
Note that the "WITH PORT <port_number>" part of the query sets the replication port,
|
||||
but it applies only to the replica. In other words, if you try to set the
|
||||
replication port as the main, a semantic exception will be thrown.
|
||||
After you have set your replica instance to the correct operating role, you can
|
||||
enable replication in the main instance by issuing the following openCypher
|
||||
command:
|
||||
```plaintext
|
||||
REGISTER REPLICA name (SYNC|ASYNC) [WITH TIMEOUT 0.5] TO <socket_address>;
|
||||
```
|
||||
|
||||
The socket address must be a string of the following form:
|
||||
|
||||
```plaintext
|
||||
"IP_ADDRESS:PORT_NUMBER"
|
||||
```
|
||||
|
||||
where IP_ADDRESS is a valid IP address, and PORT_NUMBER is a valid port number,
|
||||
both given in decimal notation.
|
||||
Note that in this case they must be separated by a single colon.
|
||||
Alternatively, one can give the socket address as:
|
||||
|
||||
```plaintext
|
||||
"IP_ADDRESS"
|
||||
```
|
||||
|
||||
where IP_ADDRESS must be a valid IP address, and the port number will be
|
||||
assumed to be the default one (we specify it to be 10000).
|
||||
|
||||
Each Memgraph instance will remember what the configuration was set to and will
|
||||
automatically resume with its role when restarted.
|
||||
|
||||
### How to Setup an Advanced Replication Scenario?
|
||||
|
||||
The configuration allows for a more advanced scenario like this:
|
||||
```plaintext
|
||||
main -[asynchronous]-> replica 1 -[semi-synchronous]-> replica 2
|
||||
```
|
||||
|
||||
To configure the above scenario, issue the following commands:
|
||||
```plaintext
|
||||
SET REPLICATION ROLE TO REPLICA; # on replica 1
|
||||
SET REPLICATION ROLE TO REPLICA; # on replica 2
|
||||
|
||||
REGISTER REPLICA replica1 ASYNC TO <replica1_sa>; # on main
|
||||
REGISTER REPLICA replica2 SYNC WITH TIMEOUT 0.5 TO <replica2_sa>; # on replica 1
|
||||
```
|
||||
|
||||
### How to See the Current Replication Status?
|
||||
|
||||
To see the replication ROLE of the current Memgraph instance, you can issue the
|
||||
following query:
|
||||
|
||||
```plaintext
|
||||
SHOW REPLICATION ROLE;
|
||||
```
|
||||
|
||||
To see the replicas of the current Memgraph instance, you can issue the
|
||||
following query:
|
||||
|
||||
```plaintext
|
||||
SHOW REPLICAS;
|
||||
```
|
||||
|
||||
To delete a replica, issue the following query:
|
||||
|
||||
```plaintext
|
||||
DELETE REPLICA 'name';
|
||||
```
|
||||
|
||||
### How to Promote a New Main?
|
||||
|
||||
When you have an already set-up cluster, to promote a new main, just set the
|
||||
replica that you want to be a main to the main role.
|
||||
|
||||
```plaintext
|
||||
SET REPLICATION ROLE TO MAIN; # on desired replica
|
||||
```
|
||||
|
||||
After the command is issued, if the original main is still alive, it won't be
|
||||
able to replicate its data to the replica (the new main) anymore and will enter
|
||||
an error state. You must ensure that at any given point in time there aren't
|
||||
two mains in the cluster.
|
||||
|
||||
## Integration with Memgraph
|
||||
|
||||
WAL `Delta`s are replicated between the replication main and replica. With
|
||||
`Delta`s, all `StorageGlobalOperation`s are also replicated. Replication is
|
||||
essentially the same as appending to the WAL.
|
||||
|
||||
Synchronous replication will occur in `Commit` and each
|
||||
`StorageGlobalOperation` handler. The storage itself guarantees that `Commit`
|
||||
will be called single-threadedly and that no `StorageGlobalOperation` will be
|
||||
executed during an active transaction. Asynchronous replication will load its
|
||||
data from already written WAL files and transmit the data to the replica. All
|
||||
data will be replicated using our RPC protocol (SLK encoded).
|
||||
|
||||
For each replica the replication main (or replica) will keep track of the
|
||||
replica's state. That way, it will know which operations must be transmitted to
|
||||
the replica and which operations can be skipped. When a replica is very stale,
|
||||
a snapshot will be transmitted to it so that it can quickly synchronize with
|
||||
the current state. All following operations will transmit WAL deltas.
|
||||
|
||||
## Reading materials
|
||||
|
||||
1. [PostgreSQL comparison of different solutions](https://www.postgresql.org/docs/12/different-replication-solutions.html)
|
||||
2. [PostgreSQL docs](https://www.postgresql.org/docs/12/runtime-config-replication.html)
|
||||
3. [MySQL reference manual](https://dev.mysql.com/doc/refman/8.0/en/replication.html)
|
||||
4. [MySQL docs](https://dev.mysql.com/doc/refman/8.0/en/replication-setup-slaves.html)
|
||||
5. [MySQL master switch](https://dev.mysql.com/doc/refman/8.0/en/replication-solutions-switch.html)
|
||||
44
environment/README.md
Normal file
44
environment/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Memgraph Build and Run Environments
|
||||
|
||||
## Toolchain Installation Procedure
|
||||
|
||||
1) Download the toolchain for your operating system from one of the following
|
||||
links (current active toolchain is `toolchain-v2`):
|
||||
|
||||
* [CentOS 7](https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/toolchain-v2/toolchain-v2-binaries-centos-7.tar.gz)
|
||||
* [CentOS 8](https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/toolchain-v2/toolchain-v2-binaries-centos-8.tar.gz)
|
||||
* [Debian 9](https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/toolchain-v2/toolchain-v2-binaries-debian-9.tar.gz)
|
||||
* [Debian 10](https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/toolchain-v2/toolchain-v2-binaries-debian-10.tar.gz)
|
||||
* [Ubuntu 18.04](https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/toolchain-v2/toolchain-v2-binaries-ubuntu-18.04.tar.gz)
|
||||
* [Ubuntu 20.04](https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/toolchain-v2/toolchain-v2-binaries-ubuntu-20.04.tar.gz)
|
||||
|
||||
2) Extract the toolchain with the following command:
|
||||
|
||||
```bash
|
||||
tar xzvf {{toolchain-archive}}.tar.gz -C /opt
|
||||
```
|
||||
|
||||
3) Check and install required toolchain runtime dependencies by executing
|
||||
(e.g., on **Debian 10**):
|
||||
|
||||
```bash
|
||||
./environment/os/debian-10.sh check TOOLCHAIN_RUN_DEPS
|
||||
./environment/os/debian-10.sh install TOOLCHAIN_RUN_DEPS
|
||||
```
|
||||
|
||||
4) Activate the toolchain:
|
||||
|
||||
```bash
|
||||
source /opt/toolchain-v2/activate
|
||||
```
|
||||
|
||||
## Toolchain Upgrade Procedure
|
||||
|
||||
1) Build a new toolchain for each supported OS (latest versions).
|
||||
2) If the new toolchain doesn't compile on some supported OS, the last
|
||||
compilable toolchain has to be used instead. In other words, the project has
|
||||
to compile on the oldest active toolchain as well. Suppose some
|
||||
changes/improvements were added when migrating to the latest toolchain; in
|
||||
that case, the maintainer has to ensure that the project still compiles on
|
||||
previous toolchains (everything from `init` script to the actual code has to
|
||||
work on all supported operating systems).
|
||||
3
environment/os/.gitignore
vendored
Normal file
3
environment/os/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*.deb
|
||||
*.rpm
|
||||
*.tar.gz
|
||||
127
environment/os/centos-7.sh
Executable file
127
environment/os/centos-7.sh
Executable file
@@ -0,0 +1,127 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc gcc-c++ make # generic build tools
|
||||
wget # used for archive download
|
||||
gnupg2 # used for archive signature verification
|
||||
tar gzip bzip2 xz unzip # used for archive unpacking
|
||||
zlib-devel # zlib library used for all builds
|
||||
expat-devel libipt-devel libbabeltrace-devel xz-devel python3-devel # gdb
|
||||
texinfo # gdb
|
||||
libcurl-devel # cmake
|
||||
readline-devel # cmake and llvm
|
||||
libffi-devel libxml2-devel perl-Digest-MD5 # llvm
|
||||
libedit-devel pcre-devel automake bison # swig
|
||||
)
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz # used for archive unpacking
|
||||
zlib # zlib library used for all builds
|
||||
expat libipt libbabeltrace xz-libs python3 # for gdb
|
||||
readline # for cmake and llvm
|
||||
libffi libxml2 # for llvm
|
||||
)
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
git # source code control
|
||||
make pkgconfig # build system
|
||||
curl wget # for downloading libs
|
||||
libuuid-devel java-11-openjdk # required by antlr
|
||||
readline-devel # for memgraph console
|
||||
python3-devel # for query modules
|
||||
openssl-devel
|
||||
libseccomp-devel
|
||||
python3 python-virtualenv python3-pip nmap-ncat # for qa, macro_benchmark and stress tests
|
||||
#
|
||||
# IMPORTANT: python3-yaml does NOT exist on CentOS
|
||||
# Install it using `pip3 install PyYAML`
|
||||
#
|
||||
PyYAML # Package name here does not correspond to the yum package!
|
||||
libcurl-devel # mg-requests
|
||||
sbcl # for custom Lisp C++ preprocessing
|
||||
rpm-build rpmlint # for RPM package building
|
||||
doxygen graphviz # source documentation generators
|
||||
which mono-complete dotnet-sdk-3.1 golang nodejs zip unzip java-11-openjdk-devel # for driver tests
|
||||
)
|
||||
list() {
|
||||
echo "$1"
|
||||
}
|
||||
check() {
|
||||
local missing=""
|
||||
for pkg in $1; do
|
||||
if [ "$pkg" == git ]; then
|
||||
if ! which "git" >/dev/null; then
|
||||
missing="git $missing"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if [ "$pkg" == "PyYAML" ]; then
|
||||
if ! python3 -c "import yaml" >/dev/null 2>/dev/null; then
|
||||
missing="$pkg $missing"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if ! yum list installed "$pkg" >/dev/null 2>/dev/null; then
|
||||
missing="$pkg $missing"
|
||||
fi
|
||||
done
|
||||
if [ "$missing" != "" ]; then
|
||||
echo "MISSING PACKAGES: $missing"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
install() {
|
||||
cd "$DIR"
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run as root."
|
||||
exit 1
|
||||
fi
|
||||
if [ "$SUDO_USER" == "" ]; then
|
||||
echo "Please run as sudo."
|
||||
exit 1
|
||||
fi
|
||||
# If GitHub Actions runner is installed, append LANG to the environment.
|
||||
# Python related tests doesn't work the LANG export.
|
||||
if [ -d "/home/gh/actions-runner" ]; then
|
||||
echo "LANG=en_US.utf8" >> /home/gh/actions-runner/.env
|
||||
else
|
||||
echo "NOTE: export LANG=en_US.utf8"
|
||||
fi
|
||||
yum install -y epel-release
|
||||
yum update -y
|
||||
yum install -y wget git python3 python3-pip
|
||||
for pkg in $1; do
|
||||
if [ "$pkg" == libipt ]; then
|
||||
if ! yum list installed libipt >/dev/null 2>/dev/null; then
|
||||
yum install -y http://repo.okay.com.mx/centos/8/x86_64/release/libipt-1.6.1-8.el8.x86_64.rpm
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if [ "$pkg" == libipt-devel ]; then
|
||||
if ! yum list installed libipt-devel >/dev/null 2>/dev/null; then
|
||||
yum install -y http://repo.okay.com.mx/centos/8/x86_64/release/libipt-devel-1.6.1-8.el8.x86_64.rpm
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if [ "$pkg" == dotnet-sdk-3.1 ]; then
|
||||
if ! yum list installed dotnet-sdk-3.1 >/dev/null 2>/dev/null; then
|
||||
wget -nv https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm -O packages-microsoft-prod.rpm
|
||||
rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm
|
||||
yum update -y
|
||||
yum install -y dotnet-sdk-3.1
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if [ "$pkg" == PyYAML ]; then
|
||||
sudo -H -u "$SUDO_USER" bash -c "pip3 install --user PyYAML"
|
||||
continue
|
||||
fi
|
||||
yum install -y "$pkg"
|
||||
done
|
||||
}
|
||||
deps=$2"[*]"
|
||||
"$1" "${!deps}"
|
||||
144
environment/os/centos-8.sh
Executable file
144
environment/os/centos-8.sh
Executable file
@@ -0,0 +1,144 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc gcc-c++ make # generic build tools
|
||||
wget # used for archive download
|
||||
gnupg2 # used for archive signature verification
|
||||
tar gzip bzip2 xz unzip # used for archive unpacking
|
||||
zlib-devel # zlib library used for all builds
|
||||
expat-devel libipt-devel libbabeltrace-devel xz-devel python36-devel texinfo # for gdb
|
||||
libcurl-devel # for cmake
|
||||
readline-devel # for cmake and llvm
|
||||
libffi-devel libxml2-devel # for llvm
|
||||
libedit-devel pcre-devel automake bison # for swig
|
||||
)
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz # used for archive unpacking
|
||||
zlib # zlib library used for all builds
|
||||
expat libipt libbabeltrace xz-libs python36 # for gdb
|
||||
readline # for cmake and llvm
|
||||
libffi libxml2 # for llvm
|
||||
)
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
git # source code control
|
||||
make pkgconf-pkg-config # build system
|
||||
curl wget # for downloading libs
|
||||
libuuid-devel java-11-openjdk # required by antlr
|
||||
readline-devel # for memgraph console
|
||||
python36-devel # for query modules
|
||||
openssl-devel
|
||||
libseccomp-devel
|
||||
python36 python3-virtualenv python3-pip nmap-ncat # for qa, macro_benchmark and stress tests
|
||||
#
|
||||
# IMPORTANT: python3-yaml does NOT exist on CentOS
|
||||
# Install it manually using `pip3 install PyYAML`
|
||||
#
|
||||
PyYAML # Package name here does not correspond to the yum package!
|
||||
libcurl-devel # mg-requests
|
||||
rpm-build rpmlint # for RPM package building
|
||||
doxygen graphviz # source documentation generators
|
||||
which mono-complete dotnet-sdk-3.1 nodejs golang zip unzip java-11-openjdk-devel # for driver tests
|
||||
sbcl # for custom Lisp C++ preprocessing
|
||||
)
|
||||
list() {
|
||||
echo "$1"
|
||||
}
|
||||
check() {
|
||||
local missing=""
|
||||
for pkg in $1; do
|
||||
if [ "$pkg" == "PyYAML" ]; then
|
||||
if ! python3 -c "import yaml" >/dev/null 2>/dev/null; then
|
||||
missing="$pkg $missing"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if ! yum list installed "$pkg" >/dev/null 2>/dev/null; then
|
||||
missing="$pkg $missing"
|
||||
fi
|
||||
done
|
||||
if [ "$missing" != "" ]; then
|
||||
echo "MISSING PACKAGES: $missing"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
install() {
|
||||
cd "$DIR"
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run as root."
|
||||
exit 1
|
||||
fi
|
||||
if [ "$SUDO_USER" == "" ]; then
|
||||
echo "Please run as sudo."
|
||||
exit 1
|
||||
fi
|
||||
# If GitHub Actions runner is installed, append LANG to the environment.
|
||||
# Python related tests doesn't work the LANG export.
|
||||
if [ -d "/home/gh/actions-runner" ]; then
|
||||
echo "LANG=en_US.utf8" >> /home/gh/actions-runner/.env
|
||||
else
|
||||
echo "NOTE: export LANG=en_US.utf8"
|
||||
fi
|
||||
dnf install -y epel-release
|
||||
dnf config-manager --set-enabled PowerTools # Required to install texinfo.
|
||||
dnf update -y
|
||||
dnf install -y wget git python36 python3-pip
|
||||
for pkg in $1; do
|
||||
if [ "$pkg" == libipt ]; then
|
||||
if ! dnf list installed libipt >/dev/null 2>/dev/null; then
|
||||
dnf install -y http://repo.okay.com.mx/centos/8/x86_64/release/libipt-1.6.1-8.el8.x86_64.rpm
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if [ "$pkg" == libipt-devel ]; then
|
||||
if ! yum list installed libipt-devel >/dev/null 2>/dev/null; then
|
||||
dnf install -y http://repo.okay.com.mx/centos/8/x86_64/release/libipt-devel-1.6.1-8.el8.x86_64.rpm
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
# Install GDB dependencies not present in the standard repos.
|
||||
# https://bugs.centos.org/view.php?id=17068
|
||||
# https://centos.pkgs.org
|
||||
# Since 2020, there is Babeltrace2 (https://babeltrace.org). Not used
|
||||
# within GDB yet (an assumption).
|
||||
if [ "$pkg" == libbabeltrace-devel ]; then
|
||||
if ! dnf list installed libbabeltrace-devel >/dev/null 2>/dev/null; then
|
||||
dnf install -y http://repo.okay.com.mx/centos/8/x86_64/release/libbabeltrace-devel-1.5.4-2.el8.x86_64.rpm
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if [ "$pkg" == sbcl ]; then
|
||||
if ! dnf list installed cl-asdf >/dev/null 2>/dev/null; then
|
||||
dnf install -y https://pkgs.dyn.su/el8/base/x86_64/cl-asdf-20101028-18.el8.noarch.rpm
|
||||
fi
|
||||
if ! dnf list installed common-lisp-controller >/dev/null 2>/dev/null; then
|
||||
dnf install -y https://pkgs.dyn.su/el8/base/x86_64/common-lisp-controller-7.4-20.el8.noarch.rpm
|
||||
fi
|
||||
if ! dnf list installed sbcl >/dev/null 2>/dev/null; then
|
||||
dnf install -y https://pkgs.dyn.su/el8/base/x86_64/sbcl-2.0.1-4.el8.x86_64.rpm
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if [ "$pkg" == dotnet-sdk-3.1 ]; then
|
||||
if ! dnf list installed dotnet-sdk-3.1 >/dev/null 2>/dev/null; then
|
||||
wget -nv https://packages.microsoft.com/config/centos/8/packages-microsoft-prod.rpm -O packages-microsoft-prod.rpm
|
||||
rpm -Uvh https://packages.microsoft.com/config/centos/8/packages-microsoft-prod.rpm
|
||||
dnf update -y
|
||||
dnf install -y dotnet-sdk-3.1
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if [ "$pkg" == PyYAML ]; then
|
||||
sudo -H -u "$SUDO_USER" bash -c "pip3 install --user PyYAML"
|
||||
continue
|
||||
fi
|
||||
dnf install -y "$pkg"
|
||||
done
|
||||
}
|
||||
deps=$2"[*]"
|
||||
"$1" "${!deps}"
|
||||
86
environment/os/debian-10.sh
Executable file
86
environment/os/debian-10.sh
Executable file
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # used for archive download
|
||||
gnupg # used for archive signature verification
|
||||
tar gzip bzip2 xz-utils unzip # used for archive unpacking
|
||||
zlib1g-dev # zlib library used for all builds
|
||||
libexpat1-dev libipt-dev libbabeltrace-dev liblzma-dev python3-dev texinfo # for gdb
|
||||
libcurl4-openssl-dev # for cmake
|
||||
libreadline-dev # for cmake and llvm
|
||||
libffi-dev libxml2-dev # for llvm
|
||||
libedit-dev libpcre3-dev automake bison # for swig
|
||||
)
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz-utils # used for archive unpacking
|
||||
zlib1g # zlib library used for all builds
|
||||
libexpat1 libipt2 libbabeltrace1 liblzma5 python3 # for gdb
|
||||
libcurl4 # for cmake
|
||||
libreadline7 # for cmake and llvm
|
||||
libffi6 libxml2 # for llvm
|
||||
)
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
git # source code control
|
||||
make pkg-config # build system
|
||||
curl wget # for downloading libs
|
||||
uuid-dev default-jre-headless # required by antlr
|
||||
libreadline-dev # for memgraph console
|
||||
libpython3-dev python3-dev # for query modules
|
||||
libssl-dev
|
||||
libseccomp-dev
|
||||
netcat # tests are using nc to wait for memgraph
|
||||
python3 virtualenv python3-virtualenv python3-pip # for qa, macro_benchmark and stress tests
|
||||
python3-yaml # for the configuration generator
|
||||
libcurl4-openssl-dev # mg-requests
|
||||
sbcl # for custom Lisp C++ preprocessing
|
||||
doxygen graphviz # source documentation generators
|
||||
mono-runtime mono-mcs zip unzip default-jdk-headless # for driver tests
|
||||
dotnet-sdk-3.1 golang nodejs npm
|
||||
)
|
||||
list() {
|
||||
echo "$1"
|
||||
}
|
||||
check() {
|
||||
check_all_dpkg "$1"
|
||||
}
|
||||
install() {
|
||||
cat >/etc/apt/sources.list <<EOF
|
||||
deb http://deb.debian.org/debian/ buster main non-free contrib
|
||||
deb-src http://deb.debian.org/debian/ buster main non-free contrib
|
||||
deb http://deb.debian.org/debian/ buster-updates main contrib non-free
|
||||
deb-src http://deb.debian.org/debian/ buster-updates main contrib non-free
|
||||
deb http://security.debian.org/debian-security buster/updates main contrib non-free
|
||||
deb-src http://security.debian.org/debian-security buster/updates main contrib non-free
|
||||
EOF
|
||||
cd "$DIR"
|
||||
apt update
|
||||
# If GitHub Actions runner is installed, append LANG to the environment.
|
||||
# Python related tests doesn't work the LANG export.
|
||||
if [ -d "/home/gh/actions-runner" ]; then
|
||||
echo "LANG=en_US.utf8" >> /home/gh/actions-runner/.env
|
||||
else
|
||||
echo "NOTE: export LANG=en_US.utf8"
|
||||
fi
|
||||
apt install -y wget
|
||||
for pkg in $1; do
|
||||
if [ "$pkg" == dotnet-sdk-3.1 ]; then
|
||||
if ! dpkg -s "$pkg" 2>/dev/null >/dev/null; then
|
||||
wget -nv https://packages.microsoft.com/config/debian/10/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
|
||||
dpkg -i packages-microsoft-prod.deb
|
||||
apt-get update
|
||||
apt-get install -y apt-transport-https dotnet-sdk-3.1
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
apt install -y "$pkg"
|
||||
done
|
||||
}
|
||||
deps=$2"[*]"
|
||||
"$1" "${!deps}"
|
||||
55
environment/os/debian-9.sh
Executable file
55
environment/os/debian-9.sh
Executable file
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # used for archive download
|
||||
gnupg # used for archive signature verification
|
||||
tar gzip bzip2 xz-utils unzip # used for archive unpacking
|
||||
zlib1g-dev # zlib library used for all builds
|
||||
libexpat1-dev libipt-dev libbabeltrace-dev libbabeltrace-ctf-dev liblzma-dev python3-dev texinfo # for gdb
|
||||
libcurl4-openssl-dev # for cmake
|
||||
libreadline-dev # for cmake and llvm
|
||||
libffi-dev libxml2-dev # for llvm
|
||||
libedit-dev libpcre3-dev automake bison # for swig
|
||||
)
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz-utils # used for archive unpacking
|
||||
zlib1g # zlib library used for all builds
|
||||
libexpat1 libipt1 libbabeltrace1 libbabeltrace-ctf1 liblzma5 python3 # for gdb
|
||||
libcurl3 # for cmake
|
||||
libreadline7 # for cmake and llvm
|
||||
libffi6 libxml2 # for llvm
|
||||
)
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
git # source code control
|
||||
make pkg-config # build system
|
||||
curl wget # for downloading libs
|
||||
uuid-dev default-jre-headless # required by antlr
|
||||
libreadline-dev # for memgraph console
|
||||
libpython3-dev python3-dev # for query modules
|
||||
libssl-dev
|
||||
libseccomp-dev
|
||||
python3 python-virtualenv python3-pip # for qa, macro_benchmark and stress tests
|
||||
python3-yaml # for the configuration generator
|
||||
libcurl4-openssl-dev # mg-requests
|
||||
sbcl # for custom Lisp C++ preprocessing
|
||||
doxygen graphviz # source documentation generators
|
||||
mono-runtime mono-mcs nodejs zip unzip default-jdk-headless # for driver tests
|
||||
)
|
||||
list() {
|
||||
echo "$1"
|
||||
}
|
||||
check() {
|
||||
check_all_dpkg "$1"
|
||||
}
|
||||
install() {
|
||||
install_all_apt "$1"
|
||||
}
|
||||
deps=$2"[*]"
|
||||
"$1" "${!deps}"
|
||||
30
environment/os/template.sh
Executable file
30
environment/os/template.sh
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
pkg
|
||||
)
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
pkg
|
||||
)
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
pkg
|
||||
)
|
||||
list() {
|
||||
echo "$1"
|
||||
}
|
||||
check() {
|
||||
echo "TODO: Implement ${FUNCNAME[0]}."
|
||||
exit 1
|
||||
}
|
||||
install() {
|
||||
echo "TODO: Implement ${FUNCNAME[0]}."
|
||||
exit 1
|
||||
}
|
||||
# http://ahmed.amayem.com/bash-indirect-expansion-exploration
|
||||
deps=$2"[*]"
|
||||
"$1" "${!deps}"
|
||||
56
environment/os/ubuntu-18.04.sh
Executable file
56
environment/os/ubuntu-18.04.sh
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # archive download
|
||||
gnupg # archive signature verification
|
||||
tar gzip bzip2 xz-utils unzip # archive unpacking
|
||||
zlib1g-dev # zlib library used for all builds
|
||||
libexpat1-dev libipt-dev libbabeltrace-dev liblzma-dev python3-dev # gdb
|
||||
texinfo # gdb
|
||||
libcurl4-openssl-dev # cmake
|
||||
libreadline-dev # cmake and llvm
|
||||
libffi-dev libxml2-dev # llvm
|
||||
libedit-dev libpcre3-dev automake bison # swig
|
||||
)
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz-utils # used for archive unpacking
|
||||
zlib1g # zlib library used for all builds
|
||||
libexpat1 libipt1 libbabeltrace1 liblzma5 python3 # for gdb
|
||||
libcurl4 # for cmake
|
||||
libreadline7 # for cmake and llvm
|
||||
libffi6 libxml2 # for llvm
|
||||
)
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
git # source code control
|
||||
make pkg-config # build system
|
||||
curl wget # downloading libs
|
||||
uuid-dev default-jre-headless # required by antlr
|
||||
libreadline-dev # memgraph console
|
||||
libpython3-dev python3-dev # for query modules
|
||||
libssl-dev
|
||||
libseccomp-dev
|
||||
python3 virtualenv python3-virtualenv python3-pip # qa, macro bench and stress tests
|
||||
python3-yaml # the configuration generator
|
||||
libcurl4-openssl-dev # mg-requests
|
||||
sbcl # custom Lisp C++ preprocessing
|
||||
doxygen graphviz # source documentation generators
|
||||
mono-runtime mono-mcs nodejs zip unzip default-jdk-headless # driver tests
|
||||
)
|
||||
list() {
|
||||
echo "$1"
|
||||
}
|
||||
check() {
|
||||
check_all_dpkg "$1"
|
||||
}
|
||||
install() {
|
||||
apt install -y $1
|
||||
}
|
||||
deps=$2"[*]"
|
||||
"$1" "${!deps}"
|
||||
78
environment/os/ubuntu-20.04.sh
Executable file
78
environment/os/ubuntu-20.04.sh
Executable file
@@ -0,0 +1,78 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # used for archive download
|
||||
gnupg # used for archive signature verification
|
||||
tar gzip bzip2 xz-utils unzip # used for archive unpacking
|
||||
zlib1g-dev # zlib library used for all builds
|
||||
libexpat1-dev libipt-dev libbabeltrace-dev liblzma-dev python3-dev texinfo # for gdb
|
||||
libcurl4-openssl-dev # for cmake
|
||||
libreadline-dev # for cmake and llvm
|
||||
libffi-dev libxml2-dev # for llvm
|
||||
libedit-dev libpcre3-dev automake bison # for swig
|
||||
)
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz-utils # used for archive unpacking
|
||||
zlib1g # zlib library used for all builds
|
||||
libexpat1 libipt2 libbabeltrace1 liblzma5 python3 # for gdb
|
||||
libcurl4 # for cmake
|
||||
libreadline8 # for cmake and llvm
|
||||
libffi7 libxml2 # for llvm
|
||||
)
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
git # source code control
|
||||
make pkg-config # build system
|
||||
curl wget # for downloading libs
|
||||
uuid-dev default-jre-headless # required by antlr
|
||||
libreadline-dev # for memgraph console
|
||||
libpython3-dev python3-dev # for query modules
|
||||
libssl-dev
|
||||
libseccomp-dev
|
||||
netcat # tests are using nc to wait for memgraph
|
||||
python3 python3-virtualenv python3-pip # for qa, macro_benchmark and stress tests
|
||||
python3-yaml # for the configuration generator
|
||||
libcurl4-openssl-dev # mg-requests
|
||||
sbcl # for custom Lisp C++ preprocessing
|
||||
doxygen graphviz # source documentation generators
|
||||
mono-runtime mono-mcs zip unzip default-jdk-headless # for driver tests
|
||||
dotnet-sdk-3.1 golang nodejs npm
|
||||
)
|
||||
list() {
|
||||
echo "$1"
|
||||
}
|
||||
check() {
|
||||
check_all_dpkg "$1"
|
||||
}
|
||||
install() {
|
||||
cd "$DIR"
|
||||
apt update
|
||||
# If GitHub Actions runner is installed, append LANG to the environment.
|
||||
# Python related tests doesn't work the LANG export.
|
||||
if [ -d "/home/gh/actions-runner" ]; then
|
||||
echo "LANG=en_US.utf8" >> /home/gh/actions-runner/.env
|
||||
else
|
||||
echo "NOTE: export LANG=en_US.utf8"
|
||||
fi
|
||||
apt install -y wget
|
||||
for pkg in $1; do
|
||||
if [ "$pkg" == dotnet-sdk-3.1 ]; then
|
||||
if ! dpkg -s dotnet-sdk-3.1 2>/dev/null >/dev/null; then
|
||||
wget -nv https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
|
||||
dpkg -i packages-microsoft-prod.deb
|
||||
apt-get update
|
||||
apt-get install -y apt-transport-https dotnet-sdk-3.1
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
apt install -y "$pkg"
|
||||
done
|
||||
}
|
||||
deps=$2"[*]"
|
||||
"$1" "${!deps}"
|
||||
651
environment/toolchain/v1.sh
Executable file
651
environment/toolchain/v1.sh
Executable file
@@ -0,0 +1,651 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
# helpers
|
||||
pushd () { command pushd "$@" > /dev/null; }
|
||||
popd () { command popd "$@" > /dev/null; }
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
CPUS=$( cat /proc/cpuinfo | grep processor | wc -l )
|
||||
cd "$DIR"
|
||||
|
||||
# toolchain version
|
||||
TOOLCHAIN_VERSION=1
|
||||
|
||||
# package versions used
|
||||
GCC_VERSION=8.3.0
|
||||
BINUTILS_VERSION=2.32
|
||||
GDB_VERSION=8.2.1
|
||||
CMAKE_VERSION=3.14.2
|
||||
CPPCHECK_VERSION=1.87
|
||||
LLVM_VERSION=8.0.0
|
||||
SWIG_VERSION=3.0.12 # used only for LLVM compilation
|
||||
|
||||
# check for installed dependencies
|
||||
DISTRO="$( egrep '^(VERSION_)?ID=' /etc/os-release | sort | cut -d '=' -f 2- | sed 's/"//g' | paste -s -d '-' )"
|
||||
case "$DISTRO" in
|
||||
debian-9)
|
||||
DEPS_MANAGER=apt-get
|
||||
DEPS_COMPILE=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # used for archive download
|
||||
gnupg # used for archive signature verification
|
||||
tar gzip bzip2 xz-utils unzip # used for archive unpacking
|
||||
zlib1g-dev # zlib library used for all builds
|
||||
libexpat1-dev libipt-dev libbabeltrace-dev libbabeltrace-ctf-dev liblzma-dev python3-dev texinfo # for gdb
|
||||
libcurl4-openssl-dev # for cmake
|
||||
libreadline-dev # for cmake and llvm
|
||||
libffi-dev libxml2-dev # for llvm
|
||||
libedit-dev libpcre3-dev automake bison # for swig
|
||||
)
|
||||
DEPS_RUN=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz-utils # used for archive unpacking
|
||||
zlib1g # zlib library used for all builds
|
||||
libexpat1 libipt1 libbabeltrace1 libbabeltrace-ctf1 liblzma5 python3 # for gdb
|
||||
libcurl3 # for cmake
|
||||
libreadline7 # for cmake and llvm
|
||||
libffi6 libxml2 # for llvm
|
||||
)
|
||||
;;
|
||||
|
||||
debian-10)
|
||||
DEPS_MANAGER=apt-get
|
||||
DEPS_COMPILE=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # used for archive download
|
||||
gnupg # used for archive signature verification
|
||||
tar gzip bzip2 xz-utils unzip # used for archive unpacking
|
||||
zlib1g-dev # zlib library used for all builds
|
||||
libexpat1-dev libipt-dev libbabeltrace-dev liblzma-dev python3-dev texinfo # for gdb
|
||||
libcurl4-openssl-dev # for cmake
|
||||
libreadline-dev # for cmake and llvm
|
||||
libffi-dev libxml2-dev # for llvm
|
||||
libedit-dev libpcre3-dev automake bison # for swig
|
||||
)
|
||||
DEPS_RUN=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz-utils # used for archive unpacking
|
||||
zlib1g # zlib library used for all builds
|
||||
libexpat1 libipt2 libbabeltrace1 liblzma5 python3 # for gdb
|
||||
libcurl4 # for cmake
|
||||
libreadline7 # for cmake and llvm
|
||||
libffi6 libxml2 # for llvm
|
||||
)
|
||||
;;
|
||||
|
||||
ubuntu-18.04)
|
||||
DEPS_MANAGER=apt-get
|
||||
DEPS_COMPILE=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # used for archive download
|
||||
gnupg # used for archive signature verification
|
||||
tar gzip bzip2 xz-utils unzip # used for archive unpacking
|
||||
zlib1g-dev # zlib library used for all builds
|
||||
libexpat1-dev libipt-dev libbabeltrace-dev liblzma-dev python3-dev texinfo # for gdb
|
||||
libcurl4-openssl-dev # for cmake
|
||||
libreadline-dev # for cmake and llvm
|
||||
libffi-dev libxml2-dev # for llvm
|
||||
libedit-dev libpcre3-dev automake bison # for swig
|
||||
)
|
||||
DEPS_RUN=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz-utils # used for archive unpacking
|
||||
zlib1g # zlib library used for all builds
|
||||
libexpat1 libipt1 libbabeltrace1 liblzma5 python3 # for gdb
|
||||
libcurl4 # for cmake
|
||||
libreadline7 # for cmake and llvm
|
||||
libffi6 libxml2 # for llvm
|
||||
)
|
||||
;;
|
||||
|
||||
centos-7)
|
||||
DEPS_MANAGER=yum
|
||||
DEPS_COMPILE=(
|
||||
coreutils gcc gcc-c++ make # generic build tools
|
||||
wget # used for archive download
|
||||
gnupg2 # used for archive signature verification
|
||||
tar gzip bzip2 xz unzip # used for archive unpacking
|
||||
zlib-devel # zlib library used for all builds
|
||||
expat-devel libipt-devel libbabeltrace-devel xz-devel python3-devel texinfo # for gdb
|
||||
libcurl-devel # for cmake
|
||||
readline-devel # for cmake and llvm
|
||||
libffi-devel libxml2-devel # for llvm
|
||||
libedit-devel pcre-devel automake bison # for swig
|
||||
)
|
||||
DEPS_RUN=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz # used for archive unpacking
|
||||
zlib # zlib library used for all builds
|
||||
expat libipt libbabeltrace xz-libs python3 # for gdb
|
||||
readline # for cmake and llvm
|
||||
libffi libxml2 # for llvm
|
||||
)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unknown distribution: $DISTRO!"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
find_missing_dependencies () {
|
||||
local message="$1"; shift
|
||||
local missing=""
|
||||
while [ "$1" != "" ]; do
|
||||
if [ "$DEPS_MANAGER" == "apt-get" ]; then
|
||||
if ! dpkg -s $1 >/dev/null 2>/dev/null; then
|
||||
missing="$1 $missing"
|
||||
fi
|
||||
elif [ "$DEPS_MANAGER" == "yum" ]; then
|
||||
if ! yum list installed $1 >/dev/null 2>/dev/null; then
|
||||
missing="$1 $missing"
|
||||
fi
|
||||
else
|
||||
echo "Invalid package manager: $DEPS_MANAGER!"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
done
|
||||
if [ "$missing" != "" ]; then
|
||||
echo "$message: $missing"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
find_missing_dependencies "Missing dependencies" ${DEPS_COMPILE[@]}
|
||||
find_missing_dependencies "All dependencies are installed, but the following runtime libraries were not found (they are probably invalid)" ${DEPS_RUN[@]}
|
||||
|
||||
# check installation directory
|
||||
NAME=toolchain-v$TOOLCHAIN_VERSION
|
||||
PREFIX=/opt/$NAME
|
||||
mkdir -p $PREFIX >/dev/null 2>/dev/null || true
|
||||
if [ ! -d $PREFIX ] || [ ! -w $PREFIX ]; then
|
||||
echo "Please make sure that the directory '$PREFIX' exists and is writable by the current user!"
|
||||
echo
|
||||
echo "If unsure, execute these commands as root:"
|
||||
echo " mkdir $PREFIX && chown $USER:$USER $PREFIX"
|
||||
echo
|
||||
echo "Press <return> when you have created the directory and granted permissions."
|
||||
# wait for the directory to be created
|
||||
while true; do
|
||||
read
|
||||
if [ ! -d $PREFIX ] || [ ! -w $PREFIX ]; then
|
||||
echo
|
||||
echo "You can't continue before you have created the directory and granted permissions!"
|
||||
echo
|
||||
echo "Press <return> when you have created the directory and granted permissions."
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# create archives directory
|
||||
mkdir -p archives
|
||||
|
||||
# download all archives
|
||||
pushd archives
|
||||
if [ ! -f gcc-$GCC_VERSION.tar.gz ]; then
|
||||
wget https://ftp.gnu.org/gnu/gcc/gcc-$GCC_VERSION/gcc-$GCC_VERSION.tar.gz
|
||||
fi
|
||||
if [ ! -f binutils-$BINUTILS_VERSION.tar.gz ]; then
|
||||
wget https://ftp.gnu.org/gnu/binutils/binutils-$BINUTILS_VERSION.tar.gz
|
||||
fi
|
||||
if [ ! -f gdb-$GDB_VERSION.tar.gz ]; then
|
||||
wget https://ftp.gnu.org/gnu/gdb/gdb-$GDB_VERSION.tar.gz
|
||||
fi
|
||||
if [ ! -f cmake-$CMAKE_VERSION.tar.gz ]; then
|
||||
wget https://github.com/Kitware/CMake/releases/download/v$CMAKE_VERSION/cmake-$CMAKE_VERSION.tar.gz
|
||||
fi
|
||||
if [ ! -f swig-$SWIG_VERSION.tar.gz ]; then
|
||||
wget https://github.com/swig/swig/archive/rel-$SWIG_VERSION.tar.gz -O swig-$SWIG_VERSION.tar.gz
|
||||
fi
|
||||
if [ ! -f cppcheck-$CPPCHECK_VERSION.tar.gz ]; then
|
||||
wget https://github.com/danmar/cppcheck/archive/$CPPCHECK_VERSION.tar.gz -O cppcheck-$CPPCHECK_VERSION.tar.gz
|
||||
fi
|
||||
if [ ! -f llvm-$LLVM_VERSION.src.tar.xz ]; then
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/llvm-$LLVM_VERSION.src.tar.xz
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/cfe-$LLVM_VERSION.src.tar.xz
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/lld-$LLVM_VERSION.src.tar.xz
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/compiler-rt-$LLVM_VERSION.src.tar.xz
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/clang-tools-extra-$LLVM_VERSION.src.tar.xz
|
||||
fi
|
||||
if [ ! -f pahole-gdb-master.zip ]; then
|
||||
wget https://github.com/PhilArmstrong/pahole-gdb/archive/master.zip -O pahole-gdb-master.zip
|
||||
fi
|
||||
|
||||
# verify all archives
|
||||
# NOTE: Verification can fail if the archive is signed by another developer. I
|
||||
# haven't added commands to download all developer GnuPG keys because the
|
||||
# download is very slow. If the verification fails for you, figure out who has
|
||||
# signed the archive and download their public key instead.
|
||||
GPG="gpg --homedir .gnupg"
|
||||
KEYSERVER="hkp://keyserver.ubuntu.com"
|
||||
mkdir -p .gnupg
|
||||
chmod 700 .gnupg
|
||||
# verify gcc
|
||||
if [ ! -f gcc-$GCC_VERSION.tar.gz.sig ]; then
|
||||
wget https://ftp.gnu.org/gnu/gcc/gcc-$GCC_VERSION/gcc-$GCC_VERSION.tar.gz.sig
|
||||
fi
|
||||
# list of valid gcc gnupg keys: https://gcc.gnu.org/mirrors.html
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0xA328C3A2C3C45C06
|
||||
$GPG --verify gcc-$GCC_VERSION.tar.gz.sig gcc-$GCC_VERSION.tar.gz
|
||||
# verify binutils
|
||||
if [ ! -f binutils-$BINUTILS_VERSION.tar.gz.sig ]; then
|
||||
wget https://ftp.gnu.org/gnu/binutils/binutils-$BINUTILS_VERSION.tar.gz.sig
|
||||
fi
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0xDD9E3C4F
|
||||
$GPG --verify binutils-$BINUTILS_VERSION.tar.gz.sig binutils-$BINUTILS_VERSION.tar.gz
|
||||
# verify gdb
|
||||
if [ ! -f gdb-$GDB_VERSION.tar.gz.sig ]; then
|
||||
wget https://ftp.gnu.org/gnu/gdb/gdb-$GDB_VERSION.tar.gz.sig
|
||||
fi
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0xFF325CF3
|
||||
$GPG --verify gdb-$GDB_VERSION.tar.gz.sig gdb-$GDB_VERSION.tar.gz
|
||||
# verify cmake
|
||||
if [ ! -f cmake-$CMAKE_VERSION-SHA-256.txt ] || [ ! -f cmake-$CMAKE_VERSION-SHA-256.txt.asc ]; then
|
||||
wget https://github.com/Kitware/CMake/releases/download/v$CMAKE_VERSION/cmake-$CMAKE_VERSION-SHA-256.txt
|
||||
wget https://github.com/Kitware/CMake/releases/download/v$CMAKE_VERSION/cmake-$CMAKE_VERSION-SHA-256.txt.asc
|
||||
# Because CentOS 7 doesn't have the `--ignore-missing` flag for `sha256sum`
|
||||
# we filter out the missing files from the sums here manually.
|
||||
cat cmake-$CMAKE_VERSION-SHA-256.txt | grep "cmake-$CMAKE_VERSION.tar.gz" > cmake-$CMAKE_VERSION-SHA-256-filtered.txt
|
||||
fi
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0xC6C265324BBEBDC350B513D02D2CEF1034921684
|
||||
sha256sum -c cmake-$CMAKE_VERSION-SHA-256-filtered.txt
|
||||
$GPG --verify cmake-$CMAKE_VERSION-SHA-256.txt.asc cmake-$CMAKE_VERSION-SHA-256.txt
|
||||
# verify llvm, cfe, lld, clang-tools-extra
|
||||
if [ ! -f llvm-$LLVM_VERSION.src.tar.xz.sig ]; then
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/llvm-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/cfe-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/lld-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/compiler-rt-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/clang-tools-extra-$LLVM_VERSION.src.tar.xz.sig
|
||||
fi
|
||||
# list of valid llvm gnupg keys: https://releases.llvm.org/download.html
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0x345AD05D
|
||||
$GPG --verify llvm-$LLVM_VERSION.src.tar.xz.sig llvm-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify cfe-$LLVM_VERSION.src.tar.xz.sig cfe-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify lld-$LLVM_VERSION.src.tar.xz.sig lld-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify compiler-rt-$LLVM_VERSION.src.tar.xz.sig compiler-rt-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify clang-tools-extra-$LLVM_VERSION.src.tar.xz.sig clang-tools-extra-$LLVM_VERSION.src.tar.xz
|
||||
popd
|
||||
|
||||
# create build directory
|
||||
mkdir -p build
|
||||
pushd build
|
||||
|
||||
# compile gcc
|
||||
if [ ! -f $PREFIX/bin/gcc ]; then
|
||||
if [ -d gcc-$GCC_VERSION ]; then
|
||||
rm -rf gcc-$GCC_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/gcc-$GCC_VERSION.tar.gz
|
||||
pushd gcc-$GCC_VERSION
|
||||
./contrib/download_prerequisites
|
||||
mkdir build && pushd build
|
||||
# influenced by: https://buildd.debian.org/status/fetch.php?pkg=gcc-8&arch=amd64&ver=8.3.0-6&stamp=1554588545
|
||||
../configure -v \
|
||||
--build=x86_64-linux-gnu \
|
||||
--host=x86_64-linux-gnu \
|
||||
--target=x86_64-linux-gnu \
|
||||
--prefix=$PREFIX \
|
||||
--disable-multilib \
|
||||
--with-system-zlib \
|
||||
--enable-checking=release \
|
||||
--enable-languages=c,c++,fortran \
|
||||
--enable-gold=yes \
|
||||
--enable-ld=yes \
|
||||
--enable-lto \
|
||||
--enable-bootstrap \
|
||||
--disable-vtable-verify \
|
||||
--disable-werror \
|
||||
--without-included-gettext \
|
||||
--enable-threads=posix \
|
||||
--enable-nls \
|
||||
--enable-clocale=gnu \
|
||||
--enable-libstdcxx-debug \
|
||||
--enable-libstdcxx-time=yes \
|
||||
--enable-gnu-unique-object \
|
||||
--enable-libmpx \
|
||||
--enable-plugin \
|
||||
--enable-default-pie \
|
||||
--with-target-system-zlib \
|
||||
--with-tune=generic \
|
||||
--without-cuda-driver
|
||||
#--program-suffix=$( printf "$GCC_VERSION" | cut -d '.' -f 1,2 ) \
|
||||
make -j$CPUS
|
||||
# make -k check # run test suite
|
||||
make install
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# activate toolchain
|
||||
export PATH=$PREFIX/bin:$PATH
|
||||
export LD_LIBRARY_PATH=$PREFIX/lib64
|
||||
|
||||
# compile binutils
|
||||
if [ ! -f $PREFIX/bin/ld.gold ]; then
|
||||
if [ -d binutils-$BINUTILS_VERSION ]; then
|
||||
rm -rf binutils-$BINUTILS_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/binutils-$BINUTILS_VERSION.tar.gz
|
||||
pushd binutils-$BINUTILS_VERSION
|
||||
mkdir build && pushd build
|
||||
# influenced by: https://buildd.debian.org/status/fetch.php?pkg=binutils&arch=amd64&ver=2.32-7&stamp=1553247092
|
||||
env \
|
||||
CC=gcc \
|
||||
CXX=g++ \
|
||||
CFLAGS="-g -O2" \
|
||||
CXXFLAGS="-g -O2" \
|
||||
LDFLAGS="" \
|
||||
../configure \
|
||||
--build=x86_64-linux-gnu \
|
||||
--host=x86_64-linux-gnu \
|
||||
--prefix=$PREFIX \
|
||||
--enable-ld=default \
|
||||
--enable-gold \
|
||||
--enable-lto \
|
||||
--enable-plugins \
|
||||
--enable-shared \
|
||||
--enable-threads \
|
||||
--with-system-zlib \
|
||||
--enable-deterministic-archives \
|
||||
--disable-compressed-debug-sections \
|
||||
--enable-new-dtags \
|
||||
--disable-werror
|
||||
make -j$CPUS
|
||||
# make -k check # run test suite
|
||||
make install
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# compile gdb
|
||||
if [ ! -f $PREFIX/bin/gdb ]; then
|
||||
if [ -d gdb-$GDB_VERSION ]; then
|
||||
rm -rf gdb-$GDB_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/gdb-$GDB_VERSION.tar.gz
|
||||
pushd gdb-$GDB_VERSION
|
||||
mkdir build && pushd build
|
||||
# https://buildd.debian.org/status/fetch.php?pkg=gdb&arch=amd64&ver=8.2.1-2&stamp=1550831554&raw=0
|
||||
env \
|
||||
CC=gcc \
|
||||
CXX=g++ \
|
||||
CFLAGS="-g -O2 -fstack-protector-strong -Wformat -Werror=format-security" \
|
||||
CXXFLAGS="-g -O2 -fstack-protector-strong -Wformat -Werror=format-security" \
|
||||
CPPFLAGS="-Wdate-time -D_FORTIFY_SOURCE=2 -fPIC" \
|
||||
LDFLAGS="-Wl,-z,relro" \
|
||||
PYTHON="" \
|
||||
../configure \
|
||||
--build=x86_64-linux-gnu \
|
||||
--host=x86_64-linux-gnu \
|
||||
--prefix=$PREFIX \
|
||||
--disable-maintainer-mode \
|
||||
--disable-dependency-tracking \
|
||||
--disable-silent-rules \
|
||||
--disable-gdbtk \
|
||||
--disable-shared \
|
||||
--without-guile \
|
||||
--with-system-gdbinit=$PREFIX/etc/gdb/gdbinit \
|
||||
--with-system-readline \
|
||||
--with-expat \
|
||||
--with-system-zlib \
|
||||
--with-lzma \
|
||||
--with-babeltrace \
|
||||
--with-intel-pt \
|
||||
--enable-tui \
|
||||
--with-python=python3
|
||||
make -j$CPUS
|
||||
make install
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# install pahole
|
||||
if [ ! -d $PREFIX/share/pahole-gdb ]; then
|
||||
unzip ../archives/pahole-gdb-master.zip
|
||||
mv pahole-gdb-master $PREFIX/share/pahole-gdb
|
||||
fi
|
||||
|
||||
# setup system gdbinit
|
||||
if [ ! -f $PREFIX/etc/gdb/gdbinit ]; then
|
||||
mkdir -p $PREFIX/etc/gdb
|
||||
cat >$PREFIX/etc/gdb/gdbinit <<EOF
|
||||
# improve formatting
|
||||
set print pretty on
|
||||
set print object on
|
||||
set print static-members on
|
||||
set print vtbl on
|
||||
set print demangle on
|
||||
set demangle-style gnu-v3
|
||||
set print sevenbit-strings off
|
||||
|
||||
# load libstdc++ pretty printers
|
||||
add-auto-load-scripts-directory $PREFIX/lib64
|
||||
add-auto-load-safe-path $PREFIX
|
||||
|
||||
# load pahole
|
||||
python
|
||||
sys.path.insert(0, "$PREFIX/share/pahole-gdb")
|
||||
import offsets
|
||||
import pahole
|
||||
end
|
||||
EOF
|
||||
fi
|
||||
|
||||
# compile cmake
|
||||
if [ ! -f $PREFIX/bin/cmake ]; then
|
||||
if [ -d cmake-$CMAKE_VERSION ]; then
|
||||
rm -rf cmake-$CMAKE_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/cmake-$CMAKE_VERSION.tar.gz
|
||||
pushd cmake-$CMAKE_VERSION
|
||||
# influenced by: https://buildd.debian.org/status/fetch.php?pkg=cmake&arch=amd64&ver=3.13.4-1&stamp=1549799837
|
||||
echo 'set(CMAKE_SKIP_RPATH ON CACHE BOOL "Skip rpath" FORCE)' >> build-flags.cmake
|
||||
echo 'set(CMAKE_USE_RELATIVE_PATHS ON CACHE BOOL "Use relative paths" FORCE)' >> build-flags.cmake
|
||||
echo 'set(CMAKE_C_FLAGS "-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2" CACHE STRING "C flags" FORCE)' >> build-flags.cmake
|
||||
echo 'set(CMAKE_CXX_FLAGS "-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2" CACHE STRING "C++ flags" FORCE)' >> build-flags.cmake
|
||||
echo 'set(CMAKE_SKIP_BOOTSTRAP_TEST ON CACHE BOOL "Skip BootstrapTest" FORCE)' >> build-flags.cmake
|
||||
echo 'set(BUILD_CursesDialog ON CACHE BOOL "Build curses GUI" FORCE)' >> build-flags.cmake
|
||||
mkdir build && pushd build
|
||||
../bootstrap \
|
||||
--prefix=$PREFIX \
|
||||
--init=../build-flags.cmake \
|
||||
--parallel=$CPUS \
|
||||
--system-curl
|
||||
make -j$CPUS
|
||||
# make test # run test suite
|
||||
make install
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# compile cppcheck
|
||||
if [ ! -f $PREFIX/bin/cppcheck ]; then
|
||||
if [ -d cppcheck-$CPPCHECK_VERSION ]; then
|
||||
rm -rf cppcheck-$CPPCHECK_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/cppcheck-$CPPCHECK_VERSION.tar.gz
|
||||
pushd cppcheck-$CPPCHECK_VERSION
|
||||
env \
|
||||
CC=gcc \
|
||||
CXX=g++ \
|
||||
PREFIX=$PREFIX \
|
||||
CFGDIR=$PREFIX/share/cppcheck/cfg \
|
||||
make -j$CPUS
|
||||
env \
|
||||
CC=gcc \
|
||||
CXX=g++ \
|
||||
PREFIX=$PREFIX \
|
||||
CFGDIR=$PREFIX/share/cppcheck/cfg \
|
||||
make install
|
||||
popd
|
||||
fi
|
||||
|
||||
# compile swig
|
||||
if [ ! -d swig-$SWIG_VERSION/install ]; then
|
||||
if [ -d swig-$SWIG_VERSION ]; then
|
||||
rm -rf swig-$SWIG_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/swig-$SWIG_VERSION.tar.gz
|
||||
mv swig-rel-$SWIG_VERSION swig-$SWIG_VERSION
|
||||
pushd swig-$SWIG_VERSION
|
||||
./autogen.sh
|
||||
mkdir build && pushd build
|
||||
../configure --prefix=$DIR/build/swig-$SWIG_VERSION/install
|
||||
make -j$CPUS
|
||||
make install
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# compile llvm
|
||||
if [ ! -f $PREFIX/bin/clang ]; then
|
||||
if [ -d llvm-$LLVM_VERSION ]; then
|
||||
rm -rf llvm-$LLVM_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/llvm-$LLVM_VERSION.src.tar.xz
|
||||
mv llvm-$LLVM_VERSION.src llvm-$LLVM_VERSION
|
||||
tar -xvf ../archives/cfe-$LLVM_VERSION.src.tar.xz
|
||||
mv cfe-$LLVM_VERSION.src llvm-$LLVM_VERSION/tools/clang
|
||||
tar -xvf ../archives/lld-$LLVM_VERSION.src.tar.xz
|
||||
mv lld-$LLVM_VERSION.src/ llvm-$LLVM_VERSION/tools/lld
|
||||
tar -xvf ../archives/compiler-rt-$LLVM_VERSION.src.tar.xz
|
||||
mv compiler-rt-$LLVM_VERSION.src/ llvm-$LLVM_VERSION/projects/compiler-rt
|
||||
tar -xvf ../archives/clang-tools-extra-$LLVM_VERSION.src.tar.xz
|
||||
mv clang-tools-extra-$LLVM_VERSION.src/ llvm-$LLVM_VERSION/tools/clang/tools/extra
|
||||
pushd llvm-$LLVM_VERSION
|
||||
mkdir build && pushd build
|
||||
# activate swig
|
||||
export PATH=$DIR/build/swig-$SWIG_VERSION/install/bin:$PATH
|
||||
# influenced by: https://buildd.debian.org/status/fetch.php?pkg=llvm-toolchain-7&arch=amd64&ver=1%3A7.0.1%7E%2Brc2-1%7Eexp1&stamp=1541506173&raw=0
|
||||
cmake .. \
|
||||
-DGCC_INSTALL_PREFIX=$PREFIX \
|
||||
-DCMAKE_C_COMPILER=$PREFIX/bin/gcc \
|
||||
-DCMAKE_CXX_COMPILER=$PREFIX/bin/g++ \
|
||||
-DCMAKE_CXX_LINK_FLAGS="-L$PREFIX/lib64 -Wl,-rpath,$PREFIX/lib64" \
|
||||
-DCMAKE_INSTALL_PREFIX=$PREFIX \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_FLAGS_RELWITHDEBINFO="-O2 -DNDEBUG" \
|
||||
-DCMAKE_CXX_FLAGS=' -fuse-ld=gold -fPIC -Wno-unused-command-line-argument -Wno-unknown-warning-option' \
|
||||
-DCMAKE_C_FLAGS=' -fuse-ld=gold -fPIC -Wno-unused-command-line-argument -Wno-unknown-warning-option' \
|
||||
-DLLVM_LINK_LLVM_DYLIB=ON \
|
||||
-DLLVM_INSTALL_UTILS=ON \
|
||||
-DLLVM_VERSION_SUFFIX= \
|
||||
-DLLVM_BUILD_LLVM_DYLIB=ON \
|
||||
-DLLVM_ENABLE_RTTI=ON \
|
||||
-DLLVM_ENABLE_FFI=ON \
|
||||
-DLLVM_BINUTILS_INCDIR=$PREFIX/include/ \
|
||||
-DLLVM_USE_PERF=yes \
|
||||
-DLIBCLANG_LIBRARY_VERSION=1 \
|
||||
-DCLANG_ENABLE_BOOTSTRAP=ON
|
||||
make -j$CPUS
|
||||
make -j$CPUS check-clang # run clang test suite
|
||||
make -j$CPUS check-lld # run lld test suite
|
||||
make install
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# create README
|
||||
if [ ! -f $PREFIX/README.md ]; then
|
||||
cat >$PREFIX/README.md <<EOF
|
||||
# Memgraph Toolchain v$TOOLCHAIN_VERSION
|
||||
|
||||
## Included tools
|
||||
|
||||
- GCC $GCC_VERSION
|
||||
- Binutils $BINUTILS_VERSION
|
||||
- GDB $GDB_VERSION
|
||||
- CMake $CMAKE_VERSION
|
||||
- Cppcheck $CPPCHECK_VERSION
|
||||
- LLVM (Clang, LLD, compiler-rt, Clang tools extra) $LLVM_VERSION
|
||||
|
||||
## Required libraries
|
||||
|
||||
In order to be able to run all of these tools you should install the following
|
||||
packages:
|
||||
|
||||
\`\`\`
|
||||
$DEPS_MANAGER install ${DEPS_RUN[@]}
|
||||
\`\`\`
|
||||
|
||||
## Usage
|
||||
|
||||
In order to use the toolchain you just have to source the activation script:
|
||||
|
||||
\`\`\`
|
||||
source $PREFIX/activate
|
||||
\`\`\`
|
||||
EOF
|
||||
fi
|
||||
|
||||
# create activation script
|
||||
if [ ! -f $PREFIX/activate ]; then
|
||||
cat >$PREFIX/activate <<EOF
|
||||
# This file must be used with "source $PREFIX/activate" *from bash*
|
||||
# You can't run it directly!
|
||||
|
||||
# check for active virtual environments
|
||||
if [ "\$( type -t deactivate )" != "" ]; then
|
||||
echo "You already have an active virtual environment!"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# check that we aren't root
|
||||
if [ "\$USER" == "root" ]; then
|
||||
echo "You shouldn't use the toolchan as root!"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# save original environment
|
||||
export ORIG_PATH=\$PATH
|
||||
export ORIG_PS1=\$PS1
|
||||
export ORIG_LD_LIBRARY_PATH=\$LD_LIBRARY_PATH
|
||||
|
||||
# activate new environment
|
||||
export PATH=$PREFIX/bin:\$PATH
|
||||
export PS1="(TOOLCHAIN) \$PS1"
|
||||
export LD_LIBRARY_PATH=$PREFIX/lib:$PREFIX/lib64
|
||||
|
||||
# disable root
|
||||
function su () {
|
||||
echo "You don't want to use root functions while using the toolchain!"
|
||||
return 1
|
||||
}
|
||||
function sudo () {
|
||||
echo "You don't want to use root functions while using the toolchain!"
|
||||
return 1
|
||||
}
|
||||
|
||||
# create deactivation function
|
||||
function deactivate() {
|
||||
export PATH=\$ORIG_PATH
|
||||
export PS1=\$ORIG_PS1
|
||||
export LD_LIBRARY_PATH=\$ORIG_LD_LIBRARY_PATH
|
||||
unset ORIG_PATH ORIG_PS1 ORIG_LD_LIBRARY_PATH
|
||||
unset -f su sudo deactivate
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
# create toolchain archive
|
||||
if [ ! -f $NAME-binaries-$DISTRO.tar.gz ]; then
|
||||
tar --owner=root --group=root -cpvzf $NAME-binaries-$DISTRO.tar.gz -C /opt $NAME
|
||||
fi
|
||||
|
||||
# output final instructions
|
||||
echo -e "\n\n"
|
||||
echo "All tools have been built. They are installed in '$PREFIX'."
|
||||
echo "In order to distribute the tools to someone else, an archive with the toolchain was created in the 'build' directory."
|
||||
echo "If you want to install the packed tools you should execute the following command:"
|
||||
echo
|
||||
echo " tar -xvzf build/$NAME-binaries.tar.gz -C /opt"
|
||||
echo
|
||||
echo "Because the tools were built on this machine, you should probably change the permissions of the installation directory using:"
|
||||
echo
|
||||
echo " OPTIONAL: chown -R root:root $PREFIX"
|
||||
echo
|
||||
echo "In order to use all of the newly compiled tools you should use the prepared activation script:"
|
||||
echo
|
||||
echo " source $PREFIX/activate"
|
||||
echo
|
||||
echo "Or, for more advanced uses, you can add the following lines to your script:"
|
||||
echo
|
||||
echo " export PATH=$PREFIX/bin:\$PATH"
|
||||
echo " export LD_LIBRARY_PATH=$PREFIX/lib:$PREFIX/lib64"
|
||||
echo
|
||||
echo "Enjoy!"
|
||||
@@ -4,88 +4,36 @@
|
||||
pushd () { command pushd "$@" > /dev/null; }
|
||||
popd () { command popd "$@" > /dev/null; }
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
CPUS=$( cat /proc/cpuinfo | grep processor | wc -l )
|
||||
CPUS=$( grep -c processor < /proc/cpuinfo )
|
||||
cd "$DIR"
|
||||
|
||||
source "$DIR/../util.sh"
|
||||
DISTRO="$(operating_system)"
|
||||
|
||||
# toolchain version
|
||||
TOOLCHAIN_VERSION=1
|
||||
TOOLCHAIN_VERSION=2
|
||||
|
||||
# package versions used
|
||||
GCC_VERSION=8.3.0
|
||||
BINUTILS_VERSION=2.32
|
||||
GDB_VERSION=8.2.1
|
||||
CMAKE_VERSION=3.14.2
|
||||
CPPCHECK_VERSION=1.87
|
||||
LLVM_VERSION=8.0.0
|
||||
SWIG_VERSION=3.0.12 # used only for LLVM compilation
|
||||
GCC_VERSION=10.2.0
|
||||
BINUTILS_VERSION=2.35.1
|
||||
case "$DISTRO" in
|
||||
centos-7) # because GDB >= 9 does NOT compile with readline6.
|
||||
GDB_VERSION=8.3
|
||||
;;
|
||||
*)
|
||||
GDB_VERSION=10.1
|
||||
;;
|
||||
esac
|
||||
CMAKE_VERSION=3.18.4
|
||||
CPPCHECK_VERSION=2.2
|
||||
LLVM_VERSION=11.0.0
|
||||
SWIG_VERSION=4.0.2 # used only for LLVM compilation
|
||||
|
||||
# check for installed dependencies
|
||||
DISTRO="$( egrep '^(VERSION_)?ID=' /etc/os-release | sort | cut -d '=' -f 2- | sed 's/"//g' | paste -s -d '-' )"
|
||||
if [ "$DISTRO" == "debian-9" ] || [ "$DISTRO" == "ubuntu-18.04" ]; then
|
||||
DEPS_MANAGER=apt-get
|
||||
DEPS_COMPILE=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # used for archive download
|
||||
gnupg # used for archive signature verification
|
||||
tar gzip bzip2 xz-utils # used for archive unpacking
|
||||
zlib1g-dev # zlib library used for all builds
|
||||
libexpat1-dev libipt-dev libbabeltrace-dev liblzma-dev python3-dev texinfo # for gdb
|
||||
libreadline-dev # for cmake and llvm
|
||||
libffi-dev libxml2-dev # for llvm
|
||||
libedit-dev libpcre3-dev automake bison # for swig
|
||||
)
|
||||
DEPS_RUN=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz-utils # used for archive unpacking
|
||||
zlib1g # zlib library used for all builds
|
||||
libexpat1 libipt1 libbabeltrace1 liblzma5 python3 # for gdb
|
||||
libreadline7 # for cmake and llvm
|
||||
libffi6 libxml2 # for llvm
|
||||
)
|
||||
elif [ "$DISTRO" == "centos-7" ]; then
|
||||
DEPS_MANAGER=yum
|
||||
DEPS_COMPILE=(
|
||||
coreutils gcc gcc-c++ make # generic build tools
|
||||
wget # used for archive download
|
||||
gnupg2 # used for archive signature verification
|
||||
tar gzip bzip2 xz # used for archive unpacking
|
||||
zlib-devel # zlib library used for all builds
|
||||
expat-devel libipt-devel libbabeltrace-devel xz-devel python36-devel texinfo # for gdb
|
||||
readline-devel # for cmake and llvm
|
||||
libffi-devel libxml2-devel # for llvm
|
||||
libedit-devel pcre-devel automake bison # for swig
|
||||
)
|
||||
DEPS_RUN=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz # used for archive unpacking
|
||||
zlib # zlib library used for all builds
|
||||
expat libipt libbabeltrace xz-libs python36 # for gdb
|
||||
readline # for cmake and llvm
|
||||
libffi libxml2 # for llvm
|
||||
)
|
||||
else
|
||||
echo "Unknown distribution: $DISTRO!"
|
||||
exit 1
|
||||
fi
|
||||
missing=""
|
||||
for dep in ${DEPS_COMPILE[@]}; do
|
||||
if [ "$DEPS_MANAGER" == "apt-get" ]; then
|
||||
if ! dpkg -s $dep >/dev/null 2>/dev/null; then
|
||||
missing="$dep $missing"
|
||||
fi
|
||||
elif [ "$DEPS_MANAGER" == "yum" ]; then
|
||||
if ! yum list installed $dep >/dev/null 2>/dev/null; then
|
||||
missing="$dep $missing"
|
||||
fi
|
||||
else
|
||||
echo "Invalid package manager: $DEPS_MANAGER!"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
if [ "$missing" != "" ]; then
|
||||
echo "Missing dependencies: $missing"
|
||||
exit 1
|
||||
fi
|
||||
# Check for the dependencies.
|
||||
echo "ALL BUILD PACKAGES: $($DIR/../os/$DISTRO.sh list TOOLCHAIN_BUILD_DEPS)"
|
||||
$DIR/../os/$DISTRO.sh check TOOLCHAIN_BUILD_DEPS
|
||||
echo "ALL RUN PACKAGES: $($DIR/../os/$DISTRO.sh list TOOLCHAIN_RUN_DEPS)"
|
||||
$DIR/../os/$DISTRO.sh check TOOLCHAIN_RUN_DEPS
|
||||
|
||||
# check installation directory
|
||||
NAME=toolchain-v$TOOLCHAIN_VERSION
|
||||
@@ -136,10 +84,14 @@ if [ ! -f cppcheck-$CPPCHECK_VERSION.tar.gz ]; then
|
||||
wget https://github.com/danmar/cppcheck/archive/$CPPCHECK_VERSION.tar.gz -O cppcheck-$CPPCHECK_VERSION.tar.gz
|
||||
fi
|
||||
if [ ! -f llvm-$LLVM_VERSION.src.tar.xz ]; then
|
||||
wget http://releases.llvm.org/$LLVM_VERSION/llvm-$LLVM_VERSION.src.tar.xz
|
||||
wget http://releases.llvm.org/$LLVM_VERSION/cfe-$LLVM_VERSION.src.tar.xz
|
||||
wget http://releases.llvm.org/$LLVM_VERSION/lld-$LLVM_VERSION.src.tar.xz
|
||||
wget http://releases.llvm.org/$LLVM_VERSION/clang-tools-extra-$LLVM_VERSION.src.tar.xz
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/llvm-$LLVM_VERSION.src.tar.xz
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/clang-$LLVM_VERSION.src.tar.xz
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/lld-$LLVM_VERSION.src.tar.xz
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/clang-tools-extra-$LLVM_VERSION.src.tar.xz
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/compiler-rt-$LLVM_VERSION.src.tar.xz
|
||||
fi
|
||||
if [ ! -f pahole-gdb-master.zip ]; then
|
||||
wget https://github.com/PhilArmstrong/pahole-gdb/archive/master.zip -O pahole-gdb-master.zip
|
||||
fi
|
||||
|
||||
# verify all archives
|
||||
@@ -156,7 +108,7 @@ if [ ! -f gcc-$GCC_VERSION.tar.gz.sig ]; then
|
||||
wget https://ftp.gnu.org/gnu/gcc/gcc-$GCC_VERSION/gcc-$GCC_VERSION.tar.gz.sig
|
||||
fi
|
||||
# list of valid gcc gnupg keys: https://gcc.gnu.org/mirrors.html
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0xC3C45C06
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0x3AB00996FC26A641
|
||||
$GPG --verify gcc-$GCC_VERSION.tar.gz.sig gcc-$GCC_VERSION.tar.gz
|
||||
# verify binutils
|
||||
if [ ! -f binutils-$BINUTILS_VERSION.tar.gz.sig ]; then
|
||||
@@ -174,23 +126,28 @@ $GPG --verify gdb-$GDB_VERSION.tar.gz.sig gdb-$GDB_VERSION.tar.gz
|
||||
if [ ! -f cmake-$CMAKE_VERSION-SHA-256.txt ] || [ ! -f cmake-$CMAKE_VERSION-SHA-256.txt.asc ]; then
|
||||
wget https://github.com/Kitware/CMake/releases/download/v$CMAKE_VERSION/cmake-$CMAKE_VERSION-SHA-256.txt
|
||||
wget https://github.com/Kitware/CMake/releases/download/v$CMAKE_VERSION/cmake-$CMAKE_VERSION-SHA-256.txt.asc
|
||||
# Because CentOS 7 doesn't have the `--ignore-missing` flag for `sha256sum`
|
||||
# we filter out the missing files from the sums here manually.
|
||||
cat cmake-$CMAKE_VERSION-SHA-256.txt | grep "cmake-$CMAKE_VERSION.tar.gz" > cmake-$CMAKE_VERSION-SHA-256-filtered.txt
|
||||
fi
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0x7BFB4EDA
|
||||
sha256sum --ignore-missing -c cmake-$CMAKE_VERSION-SHA-256.txt
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0xC6C265324BBEBDC350B513D02D2CEF1034921684
|
||||
sha256sum -c cmake-$CMAKE_VERSION-SHA-256-filtered.txt
|
||||
$GPG --verify cmake-$CMAKE_VERSION-SHA-256.txt.asc cmake-$CMAKE_VERSION-SHA-256.txt
|
||||
# verify llvm, cfe, lld, clang-tools-extra
|
||||
if [ ! -f llvm-$LLVM_VERSION.src.tar.xz.sig ]; then
|
||||
wget http://releases.llvm.org/$LLVM_VERSION/llvm-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget http://releases.llvm.org/$LLVM_VERSION/cfe-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget http://releases.llvm.org/$LLVM_VERSION/lld-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget http://releases.llvm.org/$LLVM_VERSION/clang-tools-extra-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/llvm-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/clang-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/lld-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/clang-tools-extra-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/compiler-rt-$LLVM_VERSION.src.tar.xz.sig
|
||||
fi
|
||||
# list of valid llvm gnupg keys: https://releases.llvm.org/download.html
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0x345AD05D
|
||||
$GPG --verify llvm-$LLVM_VERSION.src.tar.xz.sig llvm-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify cfe-$LLVM_VERSION.src.tar.xz.sig cfe-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify clang-$LLVM_VERSION.src.tar.xz.sig clang-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify lld-$LLVM_VERSION.src.tar.xz.sig lld-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify clang-tools-extra-$LLVM_VERSION.src.tar.xz.sig clang-tools-extra-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify compiler-rt-$LLVM_VERSION.src.tar.xz.sig compiler-rt-$LLVM_VERSION.src.tar.xz
|
||||
popd
|
||||
|
||||
# create build directory
|
||||
@@ -309,6 +266,7 @@ if [ ! -f $PREFIX/bin/gdb ]; then
|
||||
--disable-gdbtk \
|
||||
--disable-shared \
|
||||
--without-guile \
|
||||
--with-system-gdbinit=$PREFIX/etc/gdb/gdbinit \
|
||||
--with-system-readline \
|
||||
--with-expat \
|
||||
--with-system-zlib \
|
||||
@@ -322,6 +280,38 @@ if [ ! -f $PREFIX/bin/gdb ]; then
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# install pahole
|
||||
if [ ! -d $PREFIX/share/pahole-gdb ]; then
|
||||
unzip ../archives/pahole-gdb-master.zip
|
||||
mv pahole-gdb-master $PREFIX/share/pahole-gdb
|
||||
fi
|
||||
|
||||
# setup system gdbinit
|
||||
if [ ! -f $PREFIX/etc/gdb/gdbinit ]; then
|
||||
mkdir -p $PREFIX/etc/gdb
|
||||
cat >$PREFIX/etc/gdb/gdbinit <<EOF
|
||||
# improve formatting
|
||||
set print pretty on
|
||||
set print object on
|
||||
set print static-members on
|
||||
set print vtbl on
|
||||
set print demangle on
|
||||
set demangle-style gnu-v3
|
||||
set print sevenbit-strings off
|
||||
|
||||
# load libstdc++ pretty printers
|
||||
add-auto-load-scripts-directory $PREFIX/lib64
|
||||
add-auto-load-safe-path $PREFIX
|
||||
|
||||
# load pahole
|
||||
python
|
||||
sys.path.insert(0, "$PREFIX/share/pahole-gdb")
|
||||
import offsets
|
||||
import pahole
|
||||
end
|
||||
EOF
|
||||
fi
|
||||
|
||||
# compile cmake
|
||||
if [ ! -f $PREFIX/bin/cmake ]; then
|
||||
if [ -d cmake-$CMAKE_VERSION ]; then
|
||||
@@ -340,7 +330,8 @@ if [ ! -f $PREFIX/bin/cmake ]; then
|
||||
../bootstrap \
|
||||
--prefix=$PREFIX \
|
||||
--init=../build-flags.cmake \
|
||||
--parallel=$CPUS
|
||||
--parallel=$CPUS \
|
||||
--system-curl
|
||||
make -j$CPUS
|
||||
# make test # run test suite
|
||||
make install
|
||||
@@ -358,12 +349,14 @@ if [ ! -f $PREFIX/bin/cppcheck ]; then
|
||||
CC=gcc \
|
||||
CXX=g++ \
|
||||
PREFIX=$PREFIX \
|
||||
FILESDIR=$PREFIX/share/cppcheck \
|
||||
CFGDIR=$PREFIX/share/cppcheck/cfg \
|
||||
make -j$CPUS
|
||||
env \
|
||||
CC=gcc \
|
||||
CXX=g++ \
|
||||
PREFIX=$PREFIX \
|
||||
FILESDIR=$PREFIX/share/cppcheck \
|
||||
CFGDIR=$PREFIX/share/cppcheck/cfg \
|
||||
make install
|
||||
popd
|
||||
@@ -392,19 +385,20 @@ if [ ! -f $PREFIX/bin/clang ]; then
|
||||
fi
|
||||
tar -xvf ../archives/llvm-$LLVM_VERSION.src.tar.xz
|
||||
mv llvm-$LLVM_VERSION.src llvm-$LLVM_VERSION
|
||||
tar -xvf ../archives/cfe-$LLVM_VERSION.src.tar.xz
|
||||
mv cfe-$LLVM_VERSION.src llvm-$LLVM_VERSION/tools/clang
|
||||
tar -xvf ../archives/clang-$LLVM_VERSION.src.tar.xz
|
||||
mv clang-$LLVM_VERSION.src llvm-$LLVM_VERSION/tools/clang
|
||||
tar -xvf ../archives/lld-$LLVM_VERSION.src.tar.xz
|
||||
mv lld-$LLVM_VERSION.src/ llvm-$LLVM_VERSION/tools/lld
|
||||
tar -xvf ../archives/clang-tools-extra-$LLVM_VERSION.src.tar.xz
|
||||
mv clang-tools-extra-$LLVM_VERSION.src/ llvm-$LLVM_VERSION/tools/clang/tools/extra
|
||||
tar -xvf ../archives/compiler-rt-$LLVM_VERSION.src.tar.xz
|
||||
mv compiler-rt-$LLVM_VERSION.src/ llvm-$LLVM_VERSION/projects/compiler-rt
|
||||
pushd llvm-$LLVM_VERSION
|
||||
mkdir build && pushd build
|
||||
# activate swig
|
||||
export PATH=$DIR/build/swig-$SWIG_VERSION/install/bin:$PATH
|
||||
# influenced by: https://buildd.debian.org/status/fetch.php?pkg=llvm-toolchain-7&arch=amd64&ver=1%3A7.0.1%7E%2Brc2-1%7Eexp1&stamp=1541506173&raw=0
|
||||
cmake .. \
|
||||
-DGCC_INSTALL_PREFIX=$PREFIX \
|
||||
-DCMAKE_C_COMPILER=$PREFIX/bin/gcc \
|
||||
-DCMAKE_CXX_COMPILER=$PREFIX/bin/g++ \
|
||||
-DCMAKE_CXX_LINK_FLAGS="-L$PREFIX/lib64 -Wl,-rpath,$PREFIX/lib64" \
|
||||
@@ -420,9 +414,7 @@ if [ ! -f $PREFIX/bin/clang ]; then
|
||||
-DLLVM_ENABLE_RTTI=ON \
|
||||
-DLLVM_ENABLE_FFI=ON \
|
||||
-DLLVM_BINUTILS_INCDIR=$PREFIX/include/ \
|
||||
-DLLVM_USE_PERF=yes \
|
||||
-DLIBCLANG_LIBRARY_VERSION=1 \
|
||||
-DCLANG_ENABLE_BOOTSTRAP=ON
|
||||
-DLLVM_USE_PERF=yes
|
||||
make -j$CPUS
|
||||
make -j$CPUS check-clang # run clang test suite
|
||||
make -j$CPUS check-lld # run lld test suite
|
||||
@@ -442,7 +434,7 @@ if [ ! -f $PREFIX/README.md ]; then
|
||||
- GDB $GDB_VERSION
|
||||
- CMake $CMAKE_VERSION
|
||||
- Cppcheck $CPPCHECK_VERSION
|
||||
- LLVM (Clang, LLD, Clang tools extra) $LLVM_VERSION
|
||||
- LLVM (Clang, LLD, compiler-rt, Clang tools extra) $LLVM_VERSION
|
||||
|
||||
## Required libraries
|
||||
|
||||
@@ -450,7 +442,7 @@ In order to be able to run all of these tools you should install the following
|
||||
packages:
|
||||
|
||||
\`\`\`
|
||||
$DEPS_MANAGER install ${DEPS_RUN[@]}
|
||||
$($DIR/../os/$DISTRO.sh list TOOLCHAIN_RUN_DEPS)
|
||||
\`\`\`
|
||||
|
||||
## Usage
|
||||
@@ -488,7 +480,7 @@ export ORIG_LD_LIBRARY_PATH=\$LD_LIBRARY_PATH
|
||||
|
||||
# activate new environment
|
||||
export PATH=$PREFIX/bin:\$PATH
|
||||
export PS1="(TOOLCHAIN) \$PS1"
|
||||
export PS1="($NAME) \$PS1"
|
||||
export LD_LIBRARY_PATH=$PREFIX/lib:$PREFIX/lib64
|
||||
|
||||
# disable root
|
||||
45
environment/util.sh
Normal file
45
environment/util.sh
Normal file
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
|
||||
operating_system() {
|
||||
grep -E '^(VERSION_)?ID=' /etc/os-release | \
|
||||
sort | cut -d '=' -f 2- | sed 's/"//g' | paste -s -d '-'
|
||||
}
|
||||
|
||||
check_all_yum() {
|
||||
local missing=""
|
||||
for pkg in $1; do
|
||||
if ! yum list installed "$pkg" >/dev/null 2>/dev/null; then
|
||||
missing="$pkg $missing"
|
||||
fi
|
||||
done
|
||||
if [ "$missing" != "" ]; then
|
||||
echo "MISSING PACKAGES: $missing"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_all_dpkg() {
|
||||
local missing=""
|
||||
for pkg in $1; do
|
||||
if ! dpkg -s "$pkg" >/dev/null 2>/dev/null; then
|
||||
missing="$pkg $missing"
|
||||
fi
|
||||
done
|
||||
if [ "$missing" != "" ]; then
|
||||
echo "MISSING PACKAGES: $missing"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_all_dnf() {
|
||||
local missing=""
|
||||
for pkg in $1; do
|
||||
if ! dnf list installed "$pkg" >/dev/null 2>/dev/null; then
|
||||
missing="$pkg $missing"
|
||||
fi
|
||||
done
|
||||
if [ "$missing" != "" ]; then
|
||||
echo "MISSING PACKAGES: $missing"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
# distributed
|
||||
add_subdirectory(distributed)
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
Standard: "C++11"
|
||||
UseTab: Never
|
||||
DerivePointerAlignment: false
|
||||
PointerAlignment: Right
|
||||
...
|
||||
7
experimental/distributed/.gitignore
vendored
7
experimental/distributed/.gitignore
vendored
@@ -1,7 +0,0 @@
|
||||
*.out
|
||||
*.pyc
|
||||
main
|
||||
libs/
|
||||
*.cereal
|
||||
*.backup
|
||||
*.out
|
||||
@@ -1,42 +0,0 @@
|
||||
project(distributed)
|
||||
|
||||
# set directory variables
|
||||
set(src_dir ${PROJECT_SOURCE_DIR}/src)
|
||||
set(libs_dir ${PROJECT_SOURCE_DIR}/libs)
|
||||
|
||||
# includes
|
||||
include_directories(SYSTEM ${libs_dir}/cereal/include)
|
||||
include_directories(${src_dir})
|
||||
|
||||
# library from distributed sources
|
||||
file(GLOB_RECURSE src_files ${src_dir}/*.cpp)
|
||||
add_library(distributed_lib STATIC ${src_files})
|
||||
|
||||
## distributed Memgraph executable
|
||||
set(executable_name main)
|
||||
add_executable(${executable_name} ${PROJECT_SOURCE_DIR}/main.cpp)
|
||||
target_link_libraries(${executable_name} distributed_lib memgraph_lib)
|
||||
|
||||
## dummy distributed Memgraph client
|
||||
set(executable_name main-client)
|
||||
add_executable(${executable_name} ${PROJECT_SOURCE_DIR}/main-client.cpp)
|
||||
target_link_libraries(${executable_name} distributed_lib memgraph_lib)
|
||||
|
||||
# tests
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
|
||||
# copy test scripts into the build/ directory (for distributed tests)
|
||||
configure_file(${PROJECT_SOURCE_DIR}/tests/start_distributed
|
||||
${PROJECT_BINARY_DIR}/tests/start_distributed COPYONLY)
|
||||
|
||||
configure_file(${PROJECT_SOURCE_DIR}/tests/config
|
||||
${PROJECT_BINARY_DIR}/tests/config COPYONLY)
|
||||
|
||||
# copy main scripts into build/ directory (for distributed Memgraph)
|
||||
configure_file(${PROJECT_SOURCE_DIR}/start_main.py
|
||||
${PROJECT_BINARY_DIR}/start_main.py COPYONLY)
|
||||
|
||||
configure_file(${PROJECT_SOURCE_DIR}/config
|
||||
${PROJECT_BINARY_DIR}/config COPYONLY)
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
|
||||
# distributed memgraph
|
||||
|
||||
This subdirectory structure implements distributed infrastructure of Memgraph.
|
||||
|
||||
## Terminology
|
||||
|
||||
* Memgraph Node Id (mnid): a machine (processs) that runs a (distributed) Memgraph program.
|
||||
* Node: a computer that performs (distributed) work.
|
||||
* Vertex: an abstract graph concept.
|
||||
* Reactor: a unit of concurrent execution, lives on its own thread.
|
||||
* Channel: a (one-way) communication abstraction between Reactors. The reactors can be on the same machine or on different processes.
|
||||
* Message: gets sent through channels. Must be serializable if sent via network layer (library: cereal).
|
||||
* Event: arrival of a (subclass of) Message. You can register callbacks. Register exact callbacks (not for derivated/subclasses).
|
||||
* EventStream: read-end of a channel, is owned by exactly one Reactor/thread.
|
||||
* ChannelWriter: write-end of a channel, can be owned (wrote into) by multiple threads.
|
||||
|
||||
## Ownership:
|
||||
|
||||
* System, Distributed are singletons. They should be always alive.
|
||||
* ChannelWriter (write-end) should be lightweight and can be copied arbitrarily.
|
||||
* EventStream (read-end) should never be written by anyone except the owner (the reactor that created it).
|
||||
* In general: always think about who owns an object. Preferably write it in its comment block.
|
||||
|
||||
## Code Conventions
|
||||
|
||||
* Locked: A method having a "Locked..." prefix indicates that you
|
||||
have to lock the appropriate mutex before calling this function.
|
||||
* ALWAYS close channels. You will memory leak if you don't.
|
||||
Reactor::CloseChannel or Subscription::Close will do the trick.
|
||||
|
||||
## Dependencies
|
||||
|
||||
* cereal
|
||||
* <other memgraph dependencies>
|
||||
@@ -1,3 +0,0 @@
|
||||
0 127.0.0.1 10010
|
||||
1 127.0.0.1 10011
|
||||
2 127.0.0.1 10012
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
working_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
libs_dir=$working_dir/libs
|
||||
if [ ! -d $libs_dir ]; then
|
||||
mkdir $libs_dir
|
||||
fi
|
||||
|
||||
cd $libs_dir
|
||||
git clone https://github.com/USCiLab/cereal.git
|
||||
cd $libs_dir/cereal
|
||||
git checkout v1.2.2
|
||||
@@ -1,77 +0,0 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "memgraph_config.hpp"
|
||||
#include "memgraph_distributed.hpp"
|
||||
#include "memgraph_transactions.hpp"
|
||||
#include "reactors_distributed.hpp"
|
||||
|
||||
/**
|
||||
* List of queries that should be executed.
|
||||
*/
|
||||
std::vector<std::string> queries = {
|
||||
{"create vertex", "create vertex", "create vertex", "create vertex",
|
||||
"create vertex", "create vertex", "create vertex", "create vertex",
|
||||
"create vertex", "create vertex", "vertex count", "create vertex",
|
||||
"create vertex", "vertex count"}};
|
||||
|
||||
/**
|
||||
* This is the client that issues some hard-coded queries.
|
||||
*/
|
||||
class Client : public Reactor {
|
||||
public:
|
||||
Client(std::string name) : Reactor(name) {}
|
||||
|
||||
void IssueQueries(std::shared_ptr<ChannelWriter> channel_to_leader) {
|
||||
// (concurrently) create a couple of vertices
|
||||
for (int query_idx = 0; query_idx < static_cast<int64_t>(queries.size());
|
||||
++query_idx) {
|
||||
// register callback
|
||||
std::string channel_name = "query-" + std::to_string(query_idx);
|
||||
auto stream = Open(channel_name).first;
|
||||
stream->OnEventOnce().ChainOnce<ResultMsg>(
|
||||
[this, query_idx](const ResultMsg &msg, const Subscription &sub) {
|
||||
std::cout << "Result of query " << query_idx << " ("
|
||||
<< queries[query_idx] << "):" << std::endl
|
||||
<< " " << msg.result() << std::endl;
|
||||
sub.CloseChannel();
|
||||
});
|
||||
|
||||
// then issue the query (to avoid race conditions)
|
||||
std::cout << "Issuing command " << query_idx << " (" << queries[query_idx]
|
||||
<< ")" << std::endl;
|
||||
channel_to_leader->Send<QueryMsg>(channel_name, queries[query_idx]);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Run() {
|
||||
MemgraphDistributed &memgraph = MemgraphDistributed::GetInstance();
|
||||
auto mnid = memgraph.LeaderMnid();
|
||||
|
||||
memgraph.FindChannel(mnid, "master", "client-queries")
|
||||
->OnEventOnce()
|
||||
.ChainOnce<ChannelResolvedMessage>(
|
||||
[this](const ChannelResolvedMessage &msg, const Subscription &sub) {
|
||||
sub.CloseChannel();
|
||||
IssueQueries(msg.channelWriter());
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
google::InitGoogleLogging(argv[0]);
|
||||
|
||||
System &system = System::GetInstance();
|
||||
Distributed &distributed = Distributed::GetInstance();
|
||||
MemgraphDistributed &memgraph = MemgraphDistributed::GetInstance();
|
||||
memgraph.RegisterConfig(ParseConfig());
|
||||
distributed.StartServices();
|
||||
|
||||
system.Spawn<Client>("client");
|
||||
|
||||
system.AwaitShutdown();
|
||||
distributed.StopServices();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include "memgraph_config.hpp"
|
||||
#include "memgraph_distributed.hpp"
|
||||
#include "memgraph_transactions.hpp"
|
||||
#include "reactors_distributed.hpp"
|
||||
#include "storage.hpp"
|
||||
|
||||
DEFINE_uint64(my_mnid, -1, "Memgraph node id"); // TODO(zuza): this should be assigned by the leader once in the future
|
||||
|
||||
class Master : public Reactor {
|
||||
public:
|
||||
Master(std::string name, MnidT mnid) : Reactor(name), mnid_(mnid) {
|
||||
MemgraphDistributed& memgraph = MemgraphDistributed::GetInstance();
|
||||
worker_mnids_ = memgraph.GetAllMnids();
|
||||
// remove the leader (itself), because it is not a worker
|
||||
auto leader_it = std::find(worker_mnids_.begin(), worker_mnids_.end(), memgraph.LeaderMnid());
|
||||
worker_mnids_.erase(leader_it);
|
||||
}
|
||||
|
||||
virtual void Run() {
|
||||
Distributed &distributed = Distributed::GetInstance();
|
||||
|
||||
std::cout << "Master (" << mnid_ << ") @ " << distributed.network().Address()
|
||||
<< ":" << distributed.network().Port() << std::endl;
|
||||
|
||||
// TODO(zuza): check if all workers are up
|
||||
|
||||
// start listening on queries arriving from the client
|
||||
auto stream = Open("client-queries").first;
|
||||
stream->OnEvent<QueryMsg>([this](const QueryMsg &msg, const Subscription &){
|
||||
// process query message
|
||||
if (msg.query() == "create vertex") {
|
||||
InstallMakeVertex(msg.GetReturnChannelWriter());
|
||||
} else if (msg.query() == "vertex count") {
|
||||
InstallVertexCount(msg.GetReturnChannelWriter());
|
||||
} else {
|
||||
std::cerr << "unknown query" << std::endl;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Organizes communication with all workers and performs VertexCount.
|
||||
*/
|
||||
void InstallVertexCount(std::shared_ptr<ChannelWriter> return_channel) {
|
||||
// open channel through which answers will arrive
|
||||
auto channel_name = "response" + std::to_string(xid++);
|
||||
auto result = Open(channel_name).first;
|
||||
|
||||
// create struct to keep track of responses
|
||||
struct VertexCountResponse {
|
||||
VertexCountResponse(int64_t count, int64_t remaining)
|
||||
: count_(count), remaining_(remaining) {}
|
||||
|
||||
int64_t count_;
|
||||
int64_t remaining_;
|
||||
};
|
||||
|
||||
// allocate it dynamically so it lives outside the scope of this function
|
||||
// it will be deallocated once all responses arrive and channel is closed
|
||||
auto response = std::make_shared<VertexCountResponse>(0, worker_mnids_.size());
|
||||
|
||||
// register callbacks
|
||||
result->OnEvent<ResultQueryVertexCount>(
|
||||
[this, response, return_channel](const ResultQueryVertexCount &msg,
|
||||
const Subscription &sub){
|
||||
response->count_ += msg.count();
|
||||
--response->remaining_;
|
||||
if (response->remaining_ == 0) {
|
||||
sub.CloseChannel();
|
||||
return_channel->Send<ResultMsg>(std::to_string(response->count_));
|
||||
}
|
||||
});
|
||||
|
||||
// instruct workers to count vertices
|
||||
for (auto wmnid : worker_mnids_)
|
||||
VertexCount(wmnid, channel_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously counts vertices on the given node.
|
||||
*
|
||||
* @param mnid Id of the node whose vertices should be counted.
|
||||
* @param channel_name Name of the channel on which response will arrive.
|
||||
*/
|
||||
void VertexCount(MnidT mnid, std::string channel_name) {
|
||||
MemgraphDistributed::GetInstance().FindChannel(mnid, "worker", "main")
|
||||
->OnEventOnceThenClose<ChannelResolvedMessage>(
|
||||
[this, channel_name](const ChannelResolvedMessage &msg){
|
||||
msg.channelWriter()->Send<QueryVertexCount>(channel_name);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Organizes communication with a random worker and performs MakeVertex.
|
||||
*/
|
||||
void InstallMakeVertex(std::shared_ptr<ChannelWriter> return_channel) {
|
||||
// choose worker on random and instruct it to make vertex
|
||||
auto wmnid = worker_mnids_[rand() % worker_mnids_.size()];
|
||||
|
||||
// open channel through which answer will arrive
|
||||
auto channel_name = "response" + std::to_string(xid++);
|
||||
auto result = Open(channel_name).first;
|
||||
|
||||
// register callbacks for the answer
|
||||
// TODO(zuza): this is actually pretty bad because if SuccessQueryCreateVertex arrives, then
|
||||
// FailureQueryCreateVertex never gets unsubscribed. This could cause memory leaks
|
||||
// in the future (not currently since all callbacks get destroyed when channel is closed).
|
||||
// The best thing to do is to implement a ThenOnce and Either. Perhaps even a ThenClose.
|
||||
// An Either in conjunction with a failure detector event stream should eventually fail
|
||||
// the transaction and close the channel.
|
||||
result->OnEventOnceThenClose<SuccessQueryCreateVertex>(
|
||||
[this, return_channel](const SuccessQueryCreateVertex &) {
|
||||
return_channel->Send<ResultMsg>("success");
|
||||
});
|
||||
result->OnEventOnceThenClose<FailureQueryCreateVertex>(
|
||||
[this, return_channel](const FailureQueryCreateVertex &) {
|
||||
return_channel->Send<ResultMsg>("failure");
|
||||
});
|
||||
|
||||
// instruct worker to make vertex
|
||||
MakeVertex(wmnid, channel_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously creates vertex on the give node.
|
||||
*
|
||||
* @param mnid Id of the node on which vertex should be created.
|
||||
* @param channel_name Name of the channel on which response will arrive.
|
||||
*/
|
||||
void MakeVertex(MnidT mnid, std::string channel_name) {
|
||||
MemgraphDistributed::GetInstance().FindChannel(mnid, "worker", "main")
|
||||
->OnEventOnceThenClose<ChannelResolvedMessage>(
|
||||
[this, channel_name](const ChannelResolvedMessage &msg){
|
||||
msg.channelWriter()->Send<QueryCreateVertex>(channel_name);
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
// node id
|
||||
const MnidT mnid_;
|
||||
|
||||
// transaction id
|
||||
int64_t xid{0};
|
||||
|
||||
// list of ids of nodes that act as worker
|
||||
std::vector<MnidT> worker_mnids_;
|
||||
};
|
||||
|
||||
class Worker : public Reactor {
|
||||
public:
|
||||
Worker(std::string name, MnidT mnid)
|
||||
: Reactor(name), mnid_(mnid), storage_(mnid) {}
|
||||
|
||||
virtual void Run() {
|
||||
Distributed &distributed = Distributed::GetInstance();
|
||||
|
||||
std::cout << "Worker (" << mnid_ << ") @ " << distributed.network().Address()
|
||||
<< ":" << distributed.network().Port() << std::endl;
|
||||
|
||||
main_.first->OnEvent<QueryCreateVertex>([this](const QueryCreateVertex& msg,
|
||||
const Subscription &) {
|
||||
std::random_device rd; // slow random number generator
|
||||
|
||||
// succeed and fail with 50-50 (just for testing)
|
||||
// TODO: remove random failure
|
||||
if (rd() % 2 == 0) {
|
||||
storage_.MakeVertex();
|
||||
std::cout << "Vertex created" << std::endl;
|
||||
msg.GetReturnChannelWriter()->Send<SuccessQueryCreateVertex>();
|
||||
} else {
|
||||
msg.GetReturnChannelWriter()->Send<FailureQueryCreateVertex>();
|
||||
}
|
||||
});
|
||||
|
||||
main_.first->OnEvent<QueryVertexCount>([this](const QueryVertexCount &msg,
|
||||
const Subscription &){
|
||||
auto count = storage_.VertexCount();
|
||||
msg.GetReturnChannelWriter()->Send<ResultQueryVertexCount>(count);
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
const MnidT mnid_;
|
||||
ShardedStorage storage_;
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, /* remove flags from command line */ true);
|
||||
std::string logging_name = std::string(argv[0]) + "-mnid-" + std::to_string(FLAGS_my_mnid);
|
||||
google::InitGoogleLogging(logging_name.c_str());
|
||||
|
||||
System &system = System::GetInstance();
|
||||
Distributed& distributed = Distributed::GetInstance();
|
||||
MemgraphDistributed& memgraph = MemgraphDistributed::GetInstance();
|
||||
memgraph.RegisterConfig(ParseConfig());
|
||||
distributed.StartServices();
|
||||
|
||||
if (FLAGS_my_mnid == memgraph.LeaderMnid()) {
|
||||
system.Spawn<Master>("master", FLAGS_my_mnid);
|
||||
} else {
|
||||
system.Spawn<Worker>("worker", FLAGS_my_mnid);
|
||||
}
|
||||
system.AwaitShutdown();
|
||||
distributed.StopServices();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "uid.hpp"
|
||||
|
||||
enum class EdgeType { OUTGOING, INCOMING };
|
||||
|
||||
/** A vertex in the graph. Has incoming and outgoing edges which
|
||||
* are defined as global addresses of other vertices */
|
||||
class Vertex {
|
||||
public:
|
||||
Vertex(const UniqueVid &id) : id_(id) {}
|
||||
|
||||
const auto &id() const { return id_; };
|
||||
const auto &edges_out() const { return edges_out_; }
|
||||
const auto &edges_in() const { return edges_in_; }
|
||||
|
||||
void AddConnection(EdgeType edge_type, const GlobalVertAddress &gad) {
|
||||
(edge_type == EdgeType::INCOMING ? edges_in_ : edges_out_)
|
||||
.emplace_back(gad);
|
||||
}
|
||||
|
||||
/** Changes all old_address edges to have the new Memgraph node id */
|
||||
void RedirectEdges(const GlobalVertAddress& old_address, int64_t new_mnid) {
|
||||
for (auto &address : edges_in_)
|
||||
if (address == old_address) address.cur_mnid_ = new_mnid;
|
||||
for (auto &address : edges_out_)
|
||||
if (address == old_address) address.cur_mnid_ = new_mnid;
|
||||
}
|
||||
|
||||
private:
|
||||
UniqueVid id_;
|
||||
|
||||
// global addresses of vertices this vertex is connected to
|
||||
std::vector<GlobalVertAddress> edges_out_;
|
||||
std::vector<GlobalVertAddress> edges_in_;
|
||||
};
|
||||
|
||||
/**
|
||||
* A storage that doesn't assume everything is in-memory.
|
||||
*/
|
||||
class ShardedStorage {
|
||||
public:
|
||||
// Unique Memgraph node ID. Uniqueness is ensured by the (distributed) system.
|
||||
const int64_t mnid_;
|
||||
|
||||
ShardedStorage(int64_t mnid) : mnid_(mnid) {}
|
||||
|
||||
int64_t VertexCount() const { return vertices_.size(); }
|
||||
|
||||
/** Gets a vertex. */
|
||||
Vertex &GetVertex(const UniqueVid &gid) {
|
||||
auto found = vertices_.find(gid);
|
||||
assert(found != vertices_.end());
|
||||
return found->second;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of edges that cross from this
|
||||
* node into another one
|
||||
*/
|
||||
int64_t BoundaryEdgeCount() const {
|
||||
int64_t count = 0;
|
||||
auto count_f = [this, &count](const auto &edges) {
|
||||
for (const GlobalVertAddress &address : edges)
|
||||
if (address.cur_mnid_ != mnid_) count++;
|
||||
};
|
||||
for (const auto &vertex : vertices_) {
|
||||
count_f(vertex.second.edges_out());
|
||||
count_f(vertex.second.edges_in());
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/** Creates a new vertex on this node. Returns its global id */
|
||||
const UniqueVid &MakeVertex() {
|
||||
UniqueVid new_id(mnid_, next_vertex_sequence_++);
|
||||
auto new_vertex = vertices_.emplace(std::make_pair(new_id, Vertex(new_id)));
|
||||
return new_vertex.first->first;
|
||||
};
|
||||
|
||||
/** Places the existing vertex on this node */
|
||||
void PlaceVertex(const UniqueVid &gid, const Vertex &vertex) {
|
||||
vertices_.emplace(gid, vertex);
|
||||
}
|
||||
|
||||
/** Removes the vertex with the given ID from this node */
|
||||
void RemoveVertex(const UniqueVid &gid) { vertices_.erase(gid); }
|
||||
|
||||
auto begin() const { return vertices_.begin(); }
|
||||
|
||||
auto end() const { return vertices_.end(); }
|
||||
|
||||
private:
|
||||
// counter of sequences numbers of vertices created on this node
|
||||
int64_t next_vertex_sequence_{0};
|
||||
|
||||
// vertex storage of this node
|
||||
std::unordered_map<UniqueVid, Vertex> vertices_;
|
||||
};
|
||||
|
||||
/**
|
||||
* A distributed system consisting of mulitple nodes.
|
||||
* For the time being it's not modelling a distributed
|
||||
* system correctly in terms of message passing (as opposed
|
||||
* to operating on nodes and their data directly).
|
||||
*/
|
||||
class Distributed {
|
||||
public:
|
||||
/** Creates a distributed with the given number of nodes */
|
||||
Distributed(int initial_mnode_count = 0) {
|
||||
for (int mnode_id = 0; mnode_id < initial_mnode_count; mnode_id++)
|
||||
AddMnode();
|
||||
}
|
||||
|
||||
int64_t AddMnode() {
|
||||
int64_t new_mnode_id = mnodes_.size();
|
||||
mnodes_.emplace_back(new_mnode_id);
|
||||
return new_mnode_id;
|
||||
}
|
||||
|
||||
int MnodeCount() const { return mnodes_.size(); }
|
||||
|
||||
auto &GetMnode(int64_t mnode_id) { return mnodes_[mnode_id]; }
|
||||
|
||||
GlobalVertAddress MakeVertex(int64_t mnid) {
|
||||
return {mnid, mnodes_[mnid].MakeVertex()};
|
||||
}
|
||||
|
||||
Vertex &GetVertex(const GlobalVertAddress &address) {
|
||||
return mnodes_[address.cur_mnid_].GetVertex(address.uvid_);
|
||||
}
|
||||
|
||||
/** Moves a vertex with the given global id to the given mnode */
|
||||
void MoveVertex(const GlobalVertAddress &gad, int64_t destination) {
|
||||
const Vertex &vertex = GetVertex(gad);
|
||||
|
||||
// make sure that all edges to and from the vertex are updated
|
||||
for (auto &edge : vertex.edges_in())
|
||||
GetVertex(edge).RedirectEdges(gad, destination);
|
||||
for (auto &edge : vertex.edges_out())
|
||||
GetVertex(edge).RedirectEdges(gad, destination);
|
||||
|
||||
// change vertex destination
|
||||
mnodes_[destination].PlaceVertex(gad.uvid_, vertex);
|
||||
mnodes_[gad.cur_mnid_].RemoveVertex(gad.uvid_);
|
||||
}
|
||||
|
||||
void MakeEdge(const GlobalVertAddress &from, const GlobalVertAddress &to) {
|
||||
GetVertex(from).AddConnection(EdgeType::OUTGOING, to);
|
||||
GetVertex(to).AddConnection(EdgeType::INCOMING, from);
|
||||
}
|
||||
|
||||
auto begin() const { return mnodes_.begin(); }
|
||||
|
||||
auto end() const { return mnodes_.end(); }
|
||||
|
||||
private:
|
||||
std::vector<ShardedStorage> mnodes_;
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
#include "memgraph_config.hpp"
|
||||
|
||||
DEFINE_string(config_filename, "", "File containing list of all processes");
|
||||
|
||||
Config ParseConfig(const std::string &filename) {
|
||||
std::ifstream file(filename, std::ifstream::in);
|
||||
assert(file.good());
|
||||
|
||||
Config config;
|
||||
|
||||
while (file.good()) {
|
||||
MnidT mnid;
|
||||
std::string address;
|
||||
uint16_t port;
|
||||
|
||||
file >> mnid >> address >> port;
|
||||
if (file.eof())
|
||||
break;
|
||||
|
||||
config.nodes.push_back(Config::NodeConfig{mnid, address, port});
|
||||
}
|
||||
|
||||
file.close();
|
||||
return config;
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
/**
|
||||
* About config file
|
||||
*
|
||||
* Each line contains three strings:
|
||||
* memgraph node id, ip address of the worker, and port of the worker
|
||||
* Data on the first line is used to start master.
|
||||
* Data on the remaining lines is used to start workers.
|
||||
*/
|
||||
DECLARE_string(config_filename);
|
||||
|
||||
using MnidT = uint64_t;
|
||||
|
||||
struct Config {
|
||||
struct NodeConfig {
|
||||
MnidT mnid;
|
||||
std::string address;
|
||||
uint16_t port;
|
||||
};
|
||||
|
||||
std::vector<NodeConfig> nodes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse config file.
|
||||
*
|
||||
* @return config object.
|
||||
*/
|
||||
Config ParseConfig(const std::string &filename = FLAGS_config_filename);
|
||||
@@ -1,74 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "memgraph_config.hpp"
|
||||
|
||||
#include "reactors_distributed.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class MemgraphDistributed {
|
||||
private:
|
||||
using Location = std::pair<std::string, uint16_t>;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Get the (singleton) instance of MemgraphDistributed.
|
||||
*/
|
||||
static MemgraphDistributed &GetInstance() {
|
||||
static MemgraphDistributed memgraph;
|
||||
return memgraph;
|
||||
}
|
||||
|
||||
EventStream *FindChannel(MnidT mnid, const std::string &reactor,
|
||||
const std::string &channel) {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
const auto &location = mnodes_.at(mnid);
|
||||
return Distributed::GetInstance().FindChannel(
|
||||
location.first, location.second, reactor, channel);
|
||||
}
|
||||
|
||||
void RegisterConfig(const Config &config) {
|
||||
config_ = config;
|
||||
for (auto &node : config_.nodes) {
|
||||
RegisterMemgraphNode(node.mnid, node.address, node.port);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<MnidT> GetAllMnids() {
|
||||
std::vector<MnidT> mnids;
|
||||
for (auto &node : config_.nodes) {
|
||||
mnids.push_back(node.mnid);
|
||||
}
|
||||
return mnids;
|
||||
}
|
||||
|
||||
/**
|
||||
* The leader is currently the first node in the config.
|
||||
*/
|
||||
MnidT LeaderMnid() const { return config_.nodes.front().mnid; }
|
||||
|
||||
protected:
|
||||
MemgraphDistributed() {}
|
||||
|
||||
/** Register memgraph node id to the given location. */
|
||||
void RegisterMemgraphNode(MnidT mnid, const std::string &address,
|
||||
uint16_t port) {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
mnodes_[mnid] = Location(address, port);
|
||||
}
|
||||
|
||||
private:
|
||||
Config config_;
|
||||
|
||||
std::mutex mutex_;
|
||||
std::unordered_map<MnidT, Location> mnodes_;
|
||||
|
||||
MemgraphDistributed(const MemgraphDistributed &) = delete;
|
||||
MemgraphDistributed(MemgraphDistributed &&) = delete;
|
||||
MemgraphDistributed &operator=(const MemgraphDistributed &) = delete;
|
||||
MemgraphDistributed &operator=(MemgraphDistributed &&) = delete;
|
||||
};
|
||||
@@ -1,135 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "reactors_local.hpp"
|
||||
#include "reactors_distributed.hpp"
|
||||
|
||||
/**
|
||||
* Message which encapsulates query.
|
||||
* It is create on Client and sent to Master which will process it.
|
||||
*/
|
||||
class QueryMsg : public ReturnAddressMsg {
|
||||
public:
|
||||
QueryMsg(std::string return_channel, std::string query)
|
||||
: ReturnAddressMsg(return_channel), query_(query) {}
|
||||
|
||||
const std::string &query() const { return query_; }
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive &archive) {
|
||||
archive(cereal::virtual_base_class<ReturnAddressMsg>(this), query_);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Cereal needs access to default constructor.
|
||||
friend class cereal::access;
|
||||
QueryMsg() = default;
|
||||
|
||||
std::string query_;
|
||||
};
|
||||
CEREAL_REGISTER_TYPE(QueryMsg);
|
||||
|
||||
/**
|
||||
* Message which encapuslates result of a query.
|
||||
* Currently, result is string.
|
||||
*/
|
||||
class ResultMsg : public Message {
|
||||
public:
|
||||
ResultMsg(std::string result) : result_(result) {}
|
||||
|
||||
const std::string &result() const { return result_; }
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive &archive) {
|
||||
archive(cereal::virtual_base_class<Message>(this), result_);
|
||||
}
|
||||
|
||||
protected:
|
||||
friend class cereal::access;
|
||||
ResultMsg() = default;
|
||||
|
||||
std::string result_;
|
||||
};
|
||||
CEREAL_REGISTER_TYPE(ResultMsg);
|
||||
|
||||
/**
|
||||
* Below are message that are exchanged between Master and Workers.
|
||||
*/
|
||||
|
||||
class QueryCreateVertex : public ReturnAddressMsg {
|
||||
public:
|
||||
QueryCreateVertex(std::string return_channel)
|
||||
: ReturnAddressMsg(return_channel) {}
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive &archive) {
|
||||
archive(cereal::virtual_base_class<ReturnAddressMsg>(this));
|
||||
}
|
||||
|
||||
protected:
|
||||
// Cereal needs access to default constructor.
|
||||
friend class cereal::access;
|
||||
QueryCreateVertex() {}
|
||||
};
|
||||
CEREAL_REGISTER_TYPE(QueryCreateVertex);
|
||||
|
||||
class SuccessQueryCreateVertex : public Message {
|
||||
public:
|
||||
SuccessQueryCreateVertex() {}
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive &archive) {
|
||||
archive(cereal::virtual_base_class<Message>(this));
|
||||
}
|
||||
};
|
||||
CEREAL_REGISTER_TYPE(SuccessQueryCreateVertex);
|
||||
|
||||
class FailureQueryCreateVertex : public Message {
|
||||
public:
|
||||
FailureQueryCreateVertex() {}
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive &archive) {
|
||||
archive(cereal::virtual_base_class<Message>(this));
|
||||
}
|
||||
};
|
||||
CEREAL_REGISTER_TYPE(FailureQueryCreateVertex);
|
||||
|
||||
|
||||
class QueryVertexCount : public ReturnAddressMsg {
|
||||
public:
|
||||
QueryVertexCount(std::string return_channel)
|
||||
: ReturnAddressMsg(return_channel) {}
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive &archive) {
|
||||
archive(cereal::virtual_base_class<ReturnAddressMsg>(this));
|
||||
}
|
||||
|
||||
protected:
|
||||
// Cereal needs access to default constructor.
|
||||
friend class cereal::access;
|
||||
QueryVertexCount() {}
|
||||
};
|
||||
CEREAL_REGISTER_TYPE(QueryVertexCount);
|
||||
|
||||
class ResultQueryVertexCount : public Message {
|
||||
public:
|
||||
ResultQueryVertexCount(int64_t count) : count_(count) {}
|
||||
|
||||
int64_t count() const { return count_; }
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive &archive) {
|
||||
archive(cereal::virtual_base_class<Message>(this), count_);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Cereal needs access to default constructor.
|
||||
friend class cereal::access;
|
||||
ResultQueryVertexCount() {}
|
||||
|
||||
int64_t count_;
|
||||
};
|
||||
CEREAL_REGISTER_TYPE(ResultQueryVertexCount);
|
||||
@@ -1,147 +0,0 @@
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <experimental/tuple>
|
||||
#include <iostream>
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "graph.hpp"
|
||||
|
||||
namespace spinner {
|
||||
// const for balancing penalty
|
||||
double c = 2.0;
|
||||
|
||||
/**
|
||||
* Returns the index of the maximum score in the given vector.
|
||||
* If there are multiple minimums, one is chosen at random.
|
||||
*/
|
||||
auto MaxRandom(const std::vector<double> &scores) {
|
||||
std::vector<size_t> best_indices;
|
||||
double current_max = std::numeric_limits<double>::lowest();
|
||||
|
||||
for (size_t ind = 0; ind < scores.size(); ind++) {
|
||||
if (scores[ind] > current_max) {
|
||||
current_max = scores[ind];
|
||||
best_indices.clear();
|
||||
}
|
||||
if (scores[ind] == current_max) {
|
||||
best_indices.emplace_back(ind);
|
||||
}
|
||||
}
|
||||
|
||||
return best_indices[rand() % best_indices.size()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the best (highest scored) mnode
|
||||
* for the given vertex. If there are multiple mnodes with
|
||||
* the best score, vertex prefers to remain on the same mnode
|
||||
* (if among the best), or one is chosen at random.
|
||||
*
|
||||
* @param distributed - the distributed system.
|
||||
* @param vertex - the vertex which is being evaluated.
|
||||
* @param penalties - a vector of penalties (per mnode).
|
||||
* @param current_mnode - the mnode on which the given
|
||||
* vertex is currently residing.
|
||||
* @return - std::pair<int, std::vector<double>> which is a
|
||||
* pair of (best mnode, score_per_mnode).
|
||||
*/
|
||||
auto BestMnode(const Distributed &distributed, const Vertex &vertex,
|
||||
const std::vector<double> &penalties, int current_mnode) {
|
||||
// scores per mnode
|
||||
std::vector<double> scores(distributed.MnodeCount(), 0.0);
|
||||
|
||||
for (auto &edge : vertex.edges_in()) scores[edge.cur_mnid_] += 1.0;
|
||||
for (auto &edge : vertex.edges_out()) scores[edge.cur_mnid_] += 1.0;
|
||||
|
||||
for (int mnode = 0; mnode < distributed.MnodeCount(); ++mnode) {
|
||||
// normalize contribution of mnode over neighbourhood size
|
||||
scores[mnode] /= vertex.edges_out().size() + vertex.edges_in().size();
|
||||
// add balancing penalty
|
||||
scores[mnode] -= penalties[mnode];
|
||||
}
|
||||
|
||||
// pick the best destination, but prefer to stay if you can
|
||||
size_t destination = MaxRandom(scores);
|
||||
if (scores[current_mnode] == scores[destination])
|
||||
destination = current_mnode;
|
||||
|
||||
return std::make_pair(destination, scores);
|
||||
}
|
||||
|
||||
/** Indication if Spinner mnode penality is calculated based on
|
||||
* vertex or edge mnode cardinalities */
|
||||
enum class PenaltyType { Vertex, Edge };
|
||||
|
||||
/** Calcualtes Spinner penalties for mnodes in the given
|
||||
* distributed system. */
|
||||
auto Penalties(const Distributed &distributed,
|
||||
PenaltyType penalty_type = PenaltyType::Edge) {
|
||||
std::vector<double> penalties;
|
||||
int64_t total_count{0};
|
||||
|
||||
for (const auto &mnode : distributed) {
|
||||
int64_t mnode_count{0};
|
||||
switch (penalty_type) {
|
||||
case PenaltyType::Vertex:
|
||||
mnode_count += mnode.VertexCount();
|
||||
break;
|
||||
case PenaltyType::Edge:
|
||||
for (const auto &vertex_kv : mnode) {
|
||||
// Spinner counts the edges on a mnode as the sum
|
||||
// of degrees of vertices on that mnode. In that sense
|
||||
// both incoming and outgoing edges are individually
|
||||
// added...
|
||||
mnode_count += vertex_kv.second.edges_out().size();
|
||||
mnode_count += vertex_kv.second.edges_in().size();
|
||||
}
|
||||
break;
|
||||
}
|
||||
total_count += mnode_count;
|
||||
penalties.emplace_back(mnode_count);
|
||||
}
|
||||
|
||||
for (auto &penalty : penalties)
|
||||
penalty /= c * total_count / distributed.MnodeCount();
|
||||
|
||||
return penalties;
|
||||
}
|
||||
|
||||
/** Do one spinner step (modifying the given distributed) */
|
||||
void PerformSpinnerStep(Distributed &distributed) {
|
||||
auto penalties = Penalties(distributed);
|
||||
|
||||
// here a strategy can be injected for limiting
|
||||
// the number of movements performed in one step.
|
||||
// limiting could be based on (for example):
|
||||
// - limiting the number of movements per mnode
|
||||
// - limiting only to movements that are above
|
||||
// a treshold (score improvement or something)
|
||||
// - not executing on all the mnodes (also prevents
|
||||
// oscilations)
|
||||
//
|
||||
// in the first implementation just accumulate all
|
||||
// the movements and execute together.
|
||||
|
||||
// relocation info: contains the address of the Vertex
|
||||
// that needs to relocate and it's destination mnode
|
||||
std::vector<std::pair<GlobalVertAddress, int>> movements;
|
||||
|
||||
for (const ShardedStorage &mnode : distributed)
|
||||
for (const auto &gid_vertex_pair : mnode) {
|
||||
// (best destination, scores) pair for vertex
|
||||
std::pair<int, std::vector<double>> destination_scores =
|
||||
BestMnode(distributed, gid_vertex_pair.second, penalties, mnode.mnid_);
|
||||
if (destination_scores.first != mnode.mnid_)
|
||||
movements.emplace_back(GlobalVertAddress(mnode.mnid_, gid_vertex_pair.first),
|
||||
destination_scores.first);
|
||||
}
|
||||
|
||||
// execute movements. it is likely that in the real system
|
||||
// this will need to happen as a single db transaction
|
||||
for (const auto &m : movements) distributed.MoveVertex(m.first, m.second);
|
||||
}
|
||||
} // namespace spinner
|
||||
@@ -1,45 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "uid.hpp"
|
||||
|
||||
/**
|
||||
* Mock-up storage to test basic hardcoded queries.
|
||||
*
|
||||
* Current code is taken from graph.hpp. It will grow as needed to support
|
||||
* new queries, but not more. Once all functionality from graph.hpp is
|
||||
* transfered here, this file will be included in graph.hpp.
|
||||
*/
|
||||
|
||||
class Vertex {};
|
||||
|
||||
/**
|
||||
* Storage that is split over multiple nodes.
|
||||
*/
|
||||
class ShardedStorage {
|
||||
public:
|
||||
ShardedStorage(int64_t mnid) : mnid_(mnid) {}
|
||||
|
||||
/** Returns number of vertices on this node. */
|
||||
int64_t VertexCount() const { return vertices_.size(); }
|
||||
|
||||
/** Creates a new vertex on this node. Returns its global id */
|
||||
const UniqueVid &MakeVertex() {
|
||||
UniqueVid new_id(mnid_, next_vertex_sequence_++);
|
||||
auto new_vertex = vertices_.emplace(std::make_pair(new_id, Vertex()));
|
||||
return new_vertex.first->first;
|
||||
};
|
||||
|
||||
private:
|
||||
// unique Memgraph node ID
|
||||
// uniqueness is ensured by the (distributed) system
|
||||
const int64_t mnid_;
|
||||
|
||||
// counter of vertices created on this node.
|
||||
int64_t next_vertex_sequence_{0};
|
||||
|
||||
// vertex storage of this node
|
||||
std::unordered_map<UniqueVid, Vertex> vertices_;
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "utils/hashing/fnv.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* Globally unique id (in the entire distributed system) of a vertex.
|
||||
*
|
||||
* It is identified by a pair of a (original memgraph node, local vertex id)
|
||||
*/
|
||||
class UniqueVid {
|
||||
public:
|
||||
UniqueVid(int64_t orig_mnid, int64_t vid)
|
||||
: orig_mnid_(orig_mnid), vid_(vid) {}
|
||||
/** Original Memgraph node the vertex was created **/
|
||||
int64_t orig_mnid_;
|
||||
|
||||
/** Original vertex id it was assigned on creation. **/
|
||||
int64_t vid_;
|
||||
|
||||
bool operator==(const UniqueVid &other) const {
|
||||
return orig_mnid_ == other.orig_mnid_ &&
|
||||
vid_ == other.vid_;
|
||||
}
|
||||
|
||||
bool operator!=(const UniqueVid &other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
/**
|
||||
* Specifies where a vertex is in the distributed system.
|
||||
*/
|
||||
class GlobalVertAddress {
|
||||
public:
|
||||
GlobalVertAddress(int64_t cur_mnid, const UniqueVid &uvid)
|
||||
: cur_mnid_(cur_mnid), uvid_(uvid) {}
|
||||
|
||||
/** The current Memgraph node where the vertex is **/
|
||||
int64_t cur_mnid_;
|
||||
UniqueVid uvid_;
|
||||
|
||||
bool operator==(const GlobalVertAddress &other) const {
|
||||
return cur_mnid_ == other.cur_mnid_ && uvid_ == other.uvid_;
|
||||
}
|
||||
|
||||
bool operator!=(const GlobalVertAddress &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<UniqueVid> {
|
||||
size_t operator()(const UniqueVid &uid) const {
|
||||
return HashCombine<decltype(uid.orig_mnid_), decltype(uid.vid_)>()(uid.orig_mnid_, uid.vid_);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<GlobalVertAddress> {
|
||||
size_t operator()(const GlobalVertAddress &ga) const {
|
||||
return HashCombine<decltype(ga.cur_mnid_), decltype(ga.uvid_)>()(ga.cur_mnid_, ga.uvid_);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# Automatically copied to the build/ directory during Makefile (configured by cmake)
|
||||
|
||||
import os
|
||||
|
||||
terminal_command = 'gnome-terminal'
|
||||
terminal_flags = ' --geometry=200x50 ' # columns x rows
|
||||
|
||||
config_filename = 'config'
|
||||
log_dir = "logs"
|
||||
glog_flags = '--alsologtostderr --logbufsecs=0 --minloglevel=2 --log_dir="{}" '.format(log_dir)
|
||||
|
||||
def GetMainCall(my_mnid, address, port):
|
||||
ret = "./main {} --my_mnid {} --address {} --port {} --config_filename={}".format(
|
||||
glog_flags, my_mnid, address, port, config_filename)
|
||||
|
||||
print(ret)
|
||||
return ret
|
||||
|
||||
|
||||
def GetClientCall():
|
||||
ret = "./main-client {} --address 127.0.0.1 --port 10000 --config_filename={}".format(
|
||||
glog_flags, config_filename)
|
||||
print(ret)
|
||||
return ret
|
||||
|
||||
|
||||
def NamedGnomeTab(name, command):
|
||||
return " --tab -e \"bash -c 'printf \\\"\\033]0;{}\\007\\\"; {}'\" ".format(name, command)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
command = "{} {}".format(terminal_command, terminal_flags)
|
||||
command += NamedGnomeTab("client", GetClientCall())
|
||||
|
||||
f = open(config_filename, 'r')
|
||||
for line in f:
|
||||
data = line.strip().split(' ')
|
||||
my_mnid = data[0]
|
||||
address = data[1]
|
||||
port = data[2]
|
||||
command += NamedGnomeTab("mnid={}".format(my_mnid), GetMainCall(my_mnid, address, port))
|
||||
|
||||
print(command)
|
||||
os.system('mkdir -p {}'.format(log_dir))
|
||||
os.system(command)
|
||||
@@ -1,35 +0,0 @@
|
||||
# set current directory name as a test type
|
||||
get_filename_component(test_type ${CMAKE_CURRENT_SOURCE_DIR} NAME)
|
||||
|
||||
# get all cpp abs file names recursively starting from current directory
|
||||
file(GLOB_RECURSE test_type_cpps *.cpp)
|
||||
message(STATUS "Available ${test_type} cpp files are: ${test_type_cpps}")
|
||||
|
||||
include_directories(${GTEST_INCLUDE_DIR})
|
||||
|
||||
# for each cpp file build binary and register test
|
||||
foreach(test_cpp ${test_type_cpps})
|
||||
|
||||
# get exec name (remove extension from the abs path)
|
||||
get_filename_component(exec_name ${test_cpp} NAME_WE)
|
||||
|
||||
set(target_name memgraph__${test_type}__${exec_name})
|
||||
|
||||
# build exec file
|
||||
add_executable(${target_name} ${test_cpp})
|
||||
|
||||
# OUTPUT_NAME sets the real name of a target when it is built and can be
|
||||
# used to help create two targets of the same name even though CMake
|
||||
# requires unique logical target names
|
||||
set_target_properties(${target_name} PROPERTIES OUTPUT_NAME ${exec_name})
|
||||
|
||||
# link libraries
|
||||
target_link_libraries(${target_name} distributed_lib memgraph_lib)
|
||||
# gtest
|
||||
target_link_libraries(${target_name} gtest gtest_main)
|
||||
|
||||
# register test
|
||||
set(output_path ${CMAKE_BINARY_DIR}/test_results/unit/${target_name}.xml)
|
||||
add_test(${target_name} ${exec_name} --gtest_output=xml:${output_path})
|
||||
|
||||
endforeach()
|
||||
@@ -1,3 +0,0 @@
|
||||
0 127.0.0.1 10000
|
||||
2 127.0.0.1 10001
|
||||
3 127.0.0.1 10002
|
||||
@@ -1,213 +0,0 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include "memgraph_config.hpp"
|
||||
#include "reactors_distributed.hpp"
|
||||
|
||||
DEFINE_int64(my_mnid, 0, "Memgraph node id"); // TODO(zuza): this should be
|
||||
// assigned by the leader once in
|
||||
// the future
|
||||
|
||||
class MemgraphDistributed {
|
||||
private:
|
||||
using Location = std::pair<std::string, uint16_t>;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Get the (singleton) instance of MemgraphDistributed.
|
||||
*
|
||||
* More info:
|
||||
* https://stackoverflow.com/questions/1008019/c-singleton-design-pattern
|
||||
*/
|
||||
static MemgraphDistributed &GetInstance() {
|
||||
static MemgraphDistributed
|
||||
memgraph; // guaranteed to be destroyed, initialized on first use
|
||||
return memgraph;
|
||||
}
|
||||
|
||||
/** Register memgraph node id to the given location. */
|
||||
void RegisterMemgraphNode(int64_t mnid, const std::string &address,
|
||||
uint16_t port) {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
mnodes_[mnid] = Location(address, port);
|
||||
}
|
||||
|
||||
EventStream *FindChannel(int64_t mnid, const std::string &reactor,
|
||||
const std::string &channel) {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
const auto &location = mnodes_.at(mnid);
|
||||
return Distributed::GetInstance().FindChannel(
|
||||
location.first, location.second, reactor, channel);
|
||||
}
|
||||
|
||||
protected:
|
||||
MemgraphDistributed() {}
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
std::unordered_map<int64_t, Location> mnodes_;
|
||||
|
||||
MemgraphDistributed(const MemgraphDistributed &) = delete;
|
||||
MemgraphDistributed(MemgraphDistributed &&) = delete;
|
||||
MemgraphDistributed &operator=(const MemgraphDistributed &) = delete;
|
||||
MemgraphDistributed &operator=(MemgraphDistributed &&) = delete;
|
||||
};
|
||||
|
||||
/**
|
||||
* About config file
|
||||
*
|
||||
* Each line contains three strings:
|
||||
* memgraph node id, ip address of the worker, and port of the worker
|
||||
* Data on the first line is used to start master.
|
||||
* Data on the remaining lines is used to start workers.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parse config file and register processes into system.
|
||||
*
|
||||
* @return Pair (master mnid, list of worker's id).
|
||||
*/
|
||||
std::pair<int64_t, std::vector<int64_t>> ParseConfigAndRegister(
|
||||
const std::string &filename) {
|
||||
std::ifstream file(filename, std::ifstream::in);
|
||||
assert(file.good());
|
||||
int64_t master_mnid;
|
||||
std::vector<int64_t> worker_mnids;
|
||||
int64_t mnid;
|
||||
std::string address;
|
||||
uint16_t port;
|
||||
file >> master_mnid >> address >> port;
|
||||
MemgraphDistributed &memgraph = MemgraphDistributed::GetInstance();
|
||||
memgraph.RegisterMemgraphNode(master_mnid, address, port);
|
||||
while (file.good()) {
|
||||
file >> mnid >> address >> port;
|
||||
if (file.eof()) break;
|
||||
memgraph.RegisterMemgraphNode(mnid, address, port);
|
||||
worker_mnids.push_back(mnid);
|
||||
}
|
||||
file.close();
|
||||
return std::make_pair(master_mnid, worker_mnids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a text message and has a return address.
|
||||
*/
|
||||
class TextMessage : public ReturnAddressMsg {
|
||||
public:
|
||||
TextMessage(std::string reactor, std::string channel, std::string s)
|
||||
: ReturnAddressMsg(reactor, channel), text(s) {}
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive &archive) {
|
||||
archive(cereal::virtual_base_class<ReturnAddressMsg>(this), text);
|
||||
}
|
||||
|
||||
std::string text;
|
||||
|
||||
protected:
|
||||
friend class cereal::access;
|
||||
TextMessage() {} // Cereal needs access to a default constructor.
|
||||
};
|
||||
CEREAL_REGISTER_TYPE(TextMessage);
|
||||
|
||||
class Master : public Reactor {
|
||||
public:
|
||||
Master(std::string name, int64_t mnid, std::vector<int64_t> &&worker_mnids)
|
||||
: Reactor(name), mnid_(mnid), worker_mnids_(std::move(worker_mnids)) {}
|
||||
|
||||
virtual void Run() {
|
||||
MemgraphDistributed &memgraph = MemgraphDistributed::GetInstance();
|
||||
Distributed &distributed = Distributed::GetInstance();
|
||||
|
||||
std::cout << "Master (" << mnid_ << ") @ "
|
||||
<< distributed.network().Address() << ":"
|
||||
<< distributed.network().Port() << std::endl;
|
||||
|
||||
auto stream = main_.first;
|
||||
|
||||
// wait until every worker sends a ReturnAddressMsg back, then close
|
||||
stream->OnEvent<TextMessage>(
|
||||
[this](const TextMessage &msg, const Subscription &subscription) {
|
||||
std::cout << "Message from " << msg.Address() << ":" << msg.Port()
|
||||
<< " .. " << msg.text << "\n";
|
||||
++workers_seen;
|
||||
if (workers_seen == static_cast<int64_t>(worker_mnids_.size())) {
|
||||
subscription.Unsubscribe();
|
||||
// Sleep for a while so we can read output in the terminal.
|
||||
// (start_distributed runs each process in a new tab which is
|
||||
// closed immediately after process has finished)
|
||||
std::this_thread::sleep_for(std::chrono::seconds(4));
|
||||
CloseChannel("main");
|
||||
}
|
||||
});
|
||||
|
||||
// send a TextMessage to each worker
|
||||
for (auto wmnid : worker_mnids_) {
|
||||
auto stream = memgraph.FindChannel(wmnid, "worker", "main");
|
||||
stream->OnEventOnce().ChainOnce<ChannelResolvedMessage>(
|
||||
[this, stream](const ChannelResolvedMessage &msg,
|
||||
const Subscription &) {
|
||||
msg.channelWriter()->Send<TextMessage>("master", "main",
|
||||
"hi from master");
|
||||
stream->Close();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
int64_t workers_seen = 0;
|
||||
const int64_t mnid_;
|
||||
std::vector<int64_t> worker_mnids_;
|
||||
};
|
||||
|
||||
class Worker : public Reactor {
|
||||
public:
|
||||
Worker(std::string name, int64_t mnid, int64_t master_mnid)
|
||||
: Reactor(name), mnid_(mnid), master_mnid_(master_mnid) {}
|
||||
|
||||
virtual void Run() {
|
||||
Distributed &distributed = Distributed::GetInstance();
|
||||
|
||||
std::cout << "Worker (" << mnid_ << ") @ "
|
||||
<< distributed.network().Address() << ":"
|
||||
<< distributed.network().Port() << std::endl;
|
||||
|
||||
auto stream = main_.first;
|
||||
// wait until master sends us a TextMessage, then reply back and close
|
||||
stream->OnEventOnce().ChainOnce<TextMessage>(
|
||||
[this](const TextMessage &msg, const Subscription &) {
|
||||
std::cout << "Message from " << msg.Address() << ":" << msg.Port()
|
||||
<< " .. " << msg.text << "\n";
|
||||
|
||||
msg.GetReturnChannelWriter()->Send<TextMessage>("worker", "main",
|
||||
"hi from worker");
|
||||
|
||||
// Sleep for a while so we can read output in the terminal.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(4));
|
||||
CloseChannel("main");
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
const int64_t mnid_;
|
||||
const int64_t master_mnid_;
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
google::InitGoogleLogging(argv[0]);
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
System &system = System::GetInstance();
|
||||
auto mnids = ParseConfigAndRegister(FLAGS_config_filename);
|
||||
Distributed::GetInstance().StartServices();
|
||||
if (FLAGS_my_mnid == mnids.first)
|
||||
system.Spawn<Master>("master", FLAGS_my_mnid, std::move(mnids.second));
|
||||
else
|
||||
system.Spawn<Worker>("worker", FLAGS_my_mnid, mnids.first);
|
||||
system.AwaitShutdown();
|
||||
Distributed::GetInstance().StopServices();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
|
||||
#include "graph.hpp"
|
||||
|
||||
void test_global_id() {
|
||||
UniqueVid a(1, 1);
|
||||
assert(a == UniqueVid(1, 1));
|
||||
assert(a != UniqueVid(1, 2));
|
||||
assert(a != UniqueVid(2, 1));
|
||||
}
|
||||
|
||||
void test_global_address() {
|
||||
GlobalVertAddress a(1, {1, 1});
|
||||
assert(a == GlobalVertAddress(1, {1, 1}));
|
||||
assert(a != GlobalVertAddress(2, {1, 1}));
|
||||
assert(a != GlobalVertAddress(1, {2, 1}));
|
||||
}
|
||||
|
||||
void test_mnode() {
|
||||
ShardedStorage mnode0{0};
|
||||
assert(mnode0.VertexCount() == 0);
|
||||
UniqueVid n0 = mnode0.MakeVertex();
|
||||
assert(mnode0.VertexCount() == 1);
|
||||
|
||||
ShardedStorage mnode1{1};
|
||||
mnode1.PlaceVertex(n0, mnode0.GetVertex(n0));
|
||||
mnode0.RemoveVertex(n0);
|
||||
assert(mnode0.VertexCount() == 0);
|
||||
assert(mnode1.VertexCount() == 1);
|
||||
|
||||
mnode1.MakeVertex();
|
||||
assert(mnode1.VertexCount() == 2);
|
||||
assert(std::distance(mnode1.begin(), mnode1.end()) == 2);
|
||||
}
|
||||
|
||||
void test_distributed() {
|
||||
Distributed d;
|
||||
assert(d.MnodeCount() == 0);
|
||||
auto w0 = d.AddMnode();
|
||||
assert(d.MnodeCount() == 1);
|
||||
auto w1 = d.AddMnode();
|
||||
assert(d.MnodeCount() == 2);
|
||||
|
||||
GlobalVertAddress n0 = d.MakeVertex(w0);
|
||||
assert(d.GetMnode(w0).VertexCount() == 1);
|
||||
GlobalVertAddress n1 = d.MakeVertex(w1);
|
||||
|
||||
assert(d.GetVertex(n0).edges_out().size() == 0);
|
||||
assert(d.GetVertex(n0).edges_in().size() == 0);
|
||||
assert(d.GetVertex(n1).edges_out().size() == 0);
|
||||
assert(d.GetVertex(n1).edges_in().size() == 0);
|
||||
d.MakeEdge(n0, n1);
|
||||
assert(d.GetVertex(n0).edges_out().size() == 1);
|
||||
assert(d.GetVertex(n0).edges_in().size() == 0);
|
||||
assert(d.GetVertex(n1).edges_out().size() == 0);
|
||||
assert(d.GetVertex(n1).edges_in().size() == 1);
|
||||
}
|
||||
|
||||
int main() {
|
||||
test_global_id();
|
||||
test_global_address();
|
||||
test_mnode();
|
||||
test_distributed();
|
||||
std::cout << "All tests passed" << std::endl;
|
||||
}
|
||||
@@ -1,389 +0,0 @@
|
||||
// This is a deprecated implementation! It is using the deprecated AwaitEvent, I'm changing it to use OnEvent. WIP
|
||||
|
||||
// #include <atomic>
|
||||
// #include <chrono>
|
||||
// #include <cstdlib>
|
||||
// #include <iostream>
|
||||
// #include <string>
|
||||
// #include <thread>
|
||||
// #include <vector>
|
||||
|
||||
// #include "reactors_distributed.hpp"
|
||||
|
||||
// const int NUM_WORKERS = 1;
|
||||
|
||||
// class Txn : public ReturnAddressMsg {
|
||||
// public:
|
||||
// Txn(std::string reactor, std::string channel, int64_t id) : ReturnAddressMsg(reactor, channel), id_(id) {}
|
||||
// int64_t id() const { return id_; }
|
||||
|
||||
// template <class Archive>
|
||||
// void serialize(Archive &archive) {
|
||||
// archive(cereal::base_class<ReturnAddressMsg>(this), id_);
|
||||
// }
|
||||
|
||||
// private:
|
||||
// int64_t id_;
|
||||
// };
|
||||
|
||||
// class CreateNodeTxn : public Txn {
|
||||
// public:
|
||||
// CreateNodeTxn(std::string reactor, std::string channel, int64_t id) : Txn(reactor, channel, id) {}
|
||||
|
||||
// template <class Archive>
|
||||
// void serialize(Archive &archive) {
|
||||
// archive(cereal::base_class<Txn>(this));
|
||||
// }
|
||||
// };
|
||||
|
||||
// class CountNodesTxn : public Txn {
|
||||
// public:
|
||||
// CountNodesTxn(std::string reactor, std::string channel, int64_t id) : Txn(reactor, channel, id) {}
|
||||
|
||||
// template <class Archive>
|
||||
// void serialize(Archive &archive) {
|
||||
// archive(cereal::base_class<Txn>(this));
|
||||
// }
|
||||
// };
|
||||
|
||||
// class CountNodesTxnResult : public Message {
|
||||
// public:
|
||||
// CountNodesTxnResult(int64_t count) : count_(count) {}
|
||||
// int64_t count() const { return count_; }
|
||||
|
||||
// template <class Archive>
|
||||
// void serialize(Archive &archive) {
|
||||
// archive(count_);
|
||||
// }
|
||||
|
||||
// private:
|
||||
// int64_t count_;
|
||||
// };
|
||||
|
||||
// class CommitRequest : public ReturnAddressMsg {
|
||||
// public:
|
||||
// CommitRequest(std::string reactor, std::string channel, int64_t worker_id)
|
||||
// : ReturnAddressMsg(reactor, channel), worker_id_(worker_id) {}
|
||||
// int64_t worker_id() { return worker_id_; }
|
||||
|
||||
// template <class Archive>
|
||||
// void serialize(Archive &archive) {
|
||||
// archive(cereal::base_class<ReturnAddressMsg>(this), worker_id_);
|
||||
// }
|
||||
|
||||
// private:
|
||||
// int64_t worker_id_;
|
||||
// };
|
||||
|
||||
// class AbortRequest : public ReturnAddressMsg {
|
||||
// public:
|
||||
// AbortRequest(std::string reactor, std::string channel, int64_t worker_id)
|
||||
// : ReturnAddressMsg(reactor, channel), worker_id_(worker_id) {}
|
||||
// int64_t worker_id() { return worker_id_; }
|
||||
|
||||
// template <class Archive>
|
||||
// void serialize(Archive &archive) {
|
||||
// archive(cereal::base_class<ReturnAddressMsg>(this), worker_id_);
|
||||
// }
|
||||
|
||||
// private:
|
||||
// int64_t worker_id_;
|
||||
// };
|
||||
|
||||
// class CommitDirective : public Message {
|
||||
// template <class Archive>
|
||||
// void serialize(Archive &archive) {
|
||||
// archive(cereal::base_class<Message>(this));
|
||||
// }
|
||||
// };
|
||||
|
||||
// class AbortDirective : public Message {
|
||||
// template <class Archive>
|
||||
// void serialize(Archive &archive) {
|
||||
// archive(cereal::base_class<Message>(this));
|
||||
// }
|
||||
// };
|
||||
|
||||
// class Query : public Message {
|
||||
// public:
|
||||
// Query(std::string query) : Message(), query_(query) {}
|
||||
// std::string query() const { return query_; }
|
||||
|
||||
// template <class Archive>
|
||||
// void serialize(Archive &archive) {
|
||||
// archive(cereal::base_class<Message>(this), query_);
|
||||
// }
|
||||
|
||||
// private:
|
||||
// std::string query_;
|
||||
// };
|
||||
|
||||
// class Quit : public Message {
|
||||
// template <class Archive>
|
||||
// void serialize(Archive &archive) {
|
||||
// archive(cereal::base_class<Message>(this));
|
||||
// }
|
||||
// };
|
||||
|
||||
// class Master : public Reactor {
|
||||
// public:
|
||||
// Master(System *system, std::string name) : Reactor(system, name), next_xid_(1) {}
|
||||
|
||||
// virtual void Run() {
|
||||
// auto stream = main_.first;
|
||||
// FindWorkers();
|
||||
|
||||
// std::cout << "Master is active" << std::endl;
|
||||
// while (true) {
|
||||
// auto m = stream->AwaitEvent();
|
||||
// if (Query *query = dynamic_cast<Query *>(m.get())) {
|
||||
// ProcessQuery(query);
|
||||
// break; // process only the first query
|
||||
// } else if (ReturnAddressMsg *msg = dynamic_cast<ReturnAddressMsg *>(m.get())) {
|
||||
// std::cout << "ReturnAddressMsg received!" << std::endl;
|
||||
// std::cout << " Address: " << msg->Address() << std::endl;
|
||||
// std::cout << " Port: " << msg->Port() << std::endl;
|
||||
// std::cout << " Reactor: " << msg->ReactorName() << std::endl;
|
||||
// std::cout << " Channel: " << msg->ChannelName() << std::endl;
|
||||
// } else {
|
||||
// std::cerr << "unknown message\n";
|
||||
// exit(1);
|
||||
// }
|
||||
// }
|
||||
|
||||
// stream->OnEvent<Message>([this](const Message &msg, const Subscription& subscription) {
|
||||
// std::cout << "Processing Query via Callback" << std::endl;
|
||||
// const Query &query =
|
||||
// dynamic_cast<const Query &>(msg); // exception bad_cast
|
||||
// ProcessQuery(&query);
|
||||
// subscription.Unsubscribe();
|
||||
// });
|
||||
// }
|
||||
|
||||
// private:
|
||||
// void ProcessQuery(const Query *query) {
|
||||
// if (query->query() == "create node") {
|
||||
// PerformCreateNode();
|
||||
// } else if (query->query() == "count nodes") {
|
||||
// PerformCountNodes();
|
||||
// } else {
|
||||
// std::cout << "got query: " << query->query() << std::endl;
|
||||
// }
|
||||
// }
|
||||
|
||||
// void PerformCreateNode() {
|
||||
// int worker_id = rand() % NUM_WORKERS;
|
||||
// int64_t xid = GetTransactionId();
|
||||
// std::string txn_channel_name = GetTxnName(xid);
|
||||
// auto channel = Open(txn_channel_name);
|
||||
// auto stream = channel.first;
|
||||
|
||||
// channels_[worker_id]->Send<CreateNodeTxn>("master", "main", xid);
|
||||
// auto m = stream->AwaitEvent();
|
||||
// if (CommitRequest *req = dynamic_cast<CommitRequest *>(m.get())) {
|
||||
// req->GetReturnChannelWriter(system_)->Send<CommitDirective>();
|
||||
// } else if (AbortRequest *req = dynamic_cast<AbortRequest *>(m.get())) {
|
||||
// req->GetReturnChannelWriter(system_)->Send<AbortDirective>();
|
||||
// } else {
|
||||
// std::cerr << "unknown message\n";
|
||||
// exit(1);
|
||||
// }
|
||||
// CloseChannel(txn_channel_name);
|
||||
// }
|
||||
|
||||
// void PerformCountNodes() {
|
||||
// int64_t xid = GetTransactionId();
|
||||
// std::string txn_channel_name = GetTxnName(xid);
|
||||
// auto channel = Open(txn_channel_name);
|
||||
// auto stream = channel.first;
|
||||
// for (int w_id = 0; w_id < NUM_WORKERS; ++w_id)
|
||||
// channels_[w_id]->Send<CountNodesTxn>("master", "main", xid);
|
||||
|
||||
// std::vector<std::shared_ptr<Channel>> txn_channels;
|
||||
// txn_channels.resize(NUM_WORKERS, nullptr);
|
||||
// bool commit = true;
|
||||
// for (int responds = 0; responds < NUM_WORKERS; ++responds) {
|
||||
// auto m = stream->AwaitEvent();
|
||||
// if (CommitRequest *req = dynamic_cast<CommitRequest *>(m.get())) {
|
||||
// txn_channels[req->worker_id()] = req->GetReturnChannelWriter(system_);
|
||||
// commit &= true;
|
||||
// } else if (AbortRequest *req = dynamic_cast<AbortRequest *>(m.get())) {
|
||||
// txn_channels[req->worker_id()] = req->GetReturnChannelWriter(system_);
|
||||
// commit = false;
|
||||
// } else {
|
||||
// std::cerr << "unknown message\n";
|
||||
// exit(1);
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (commit) {
|
||||
// for (int w_id = 0; w_id < NUM_WORKERS; ++w_id)
|
||||
// txn_channels[w_id]->Send<CommitDirective>();
|
||||
// } else {
|
||||
// for (int w_id = 0; w_id < NUM_WORKERS; ++w_id)
|
||||
// txn_channels[w_id]->Send<AbortDirective>();
|
||||
// }
|
||||
|
||||
// int64_t count = 0;
|
||||
// for (int responds = 0; responds < NUM_WORKERS; ++responds) {
|
||||
// auto m = stream->AwaitEvent();
|
||||
// if (CountNodesTxnResult *cnt =
|
||||
// dynamic_cast<CountNodesTxnResult *>(m.get())) {
|
||||
// count += cnt->count();
|
||||
// } else {
|
||||
// std::cerr << "unknown message\n";
|
||||
// exit(1);
|
||||
// }
|
||||
// }
|
||||
|
||||
// CloseChannel(txn_channel_name);
|
||||
// std::cout << "graph has " << count << " vertices" << std::endl;
|
||||
// }
|
||||
|
||||
// int64_t GetTransactionId() { return next_xid_++; }
|
||||
|
||||
// std::string GetWorkerName(int worker_id) {
|
||||
// return "worker" + std::to_string(worker_id);
|
||||
// }
|
||||
|
||||
// std::string GetTxnName(int txn_id) { return "txn" + std::to_string(txn_id); }
|
||||
|
||||
// void FindWorkers() {
|
||||
// channels_.resize(NUM_WORKERS, nullptr);
|
||||
// int workers_found = 0;
|
||||
// while (workers_found < NUM_WORKERS) {
|
||||
// for (int64_t w_id = 0; w_id < NUM_WORKERS; ++w_id) {
|
||||
// if (channels_[w_id] == nullptr) {
|
||||
// // TODO: Resolve worker channel using the network service.
|
||||
// channels_[w_id] = system_->FindChannel(GetWorkerName(w_id), "main");
|
||||
// if (channels_[w_id] != nullptr) ++workers_found;
|
||||
// }
|
||||
// }
|
||||
// if (workers_found < NUM_WORKERS)
|
||||
// std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
// }
|
||||
// }
|
||||
|
||||
// // TODO: Why is master atomic, it should be unique?
|
||||
// std::atomic<int64_t> next_xid_;
|
||||
// std::vector<std::shared_ptr<Channel>> channels_;
|
||||
// };
|
||||
|
||||
// class Worker : public Reactor {
|
||||
// public:
|
||||
// Worker(System *system, std::string name, int64_t id) : Reactor(system, name),
|
||||
// worker_id_(id) {}
|
||||
|
||||
// virtual void Run() {
|
||||
// std::cout << "worker " << worker_id_ << " is active" << std::endl;
|
||||
// auto stream = main_.first;
|
||||
// FindMaster();
|
||||
// while (true) {
|
||||
// auto m = stream->AwaitEvent();
|
||||
// if (Txn *txn = dynamic_cast<Txn *>(m.get())) {
|
||||
// HandleTransaction(txn);
|
||||
// } else {
|
||||
// std::cerr << "unknown message\n";
|
||||
// exit(1);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// private:
|
||||
// void HandleTransaction(Txn *txn) {
|
||||
// if (CreateNodeTxn *create_txn = dynamic_cast<CreateNodeTxn *>(txn)) {
|
||||
// HandleCreateNode(create_txn);
|
||||
// } else if (CountNodesTxn *cnt_txn = dynamic_cast<CountNodesTxn *>(txn)) {
|
||||
// HandleCountNodes(cnt_txn);
|
||||
// } else {
|
||||
// std::cerr << "unknown transaction\n";
|
||||
// exit(1);
|
||||
// }
|
||||
// }
|
||||
|
||||
// void HandleCreateNode(CreateNodeTxn *txn) {
|
||||
// auto channel = Open(GetTxnChannelName(txn->id()));
|
||||
// auto stream = channel.first;
|
||||
// auto masterChannel = txn->GetReturnChannelWriter(system_);
|
||||
// // TODO: Do the actual commit.
|
||||
// masterChannel->Send<CommitRequest>("master", "main", worker_id_);
|
||||
// auto m = stream->AwaitEvent();
|
||||
// if (dynamic_cast<CommitDirective *>(m.get())) {
|
||||
// // TODO: storage_.CreateNode();
|
||||
// } else if (dynamic_cast<AbortDirective *>(m.get())) {
|
||||
// // TODO: Rollback.
|
||||
// } else {
|
||||
// std::cerr << "unknown message\n";
|
||||
// exit(1);
|
||||
// }
|
||||
// CloseChannel(GetTxnChannelName(txn->id()));
|
||||
// }
|
||||
|
||||
// void HandleCountNodes(CountNodesTxn *txn) {
|
||||
// auto channel = Open(GetTxnChannelName(txn->id()));
|
||||
// auto stream = channel.first;
|
||||
// auto masterChannel = txn->GetReturnChannelWriter(system_);
|
||||
|
||||
// // TODO: Fix this hack -- use the storage.
|
||||
// int num = 123;
|
||||
|
||||
// masterChannel->Send<CommitRequest>("master", "main", worker_id_);
|
||||
// auto m = stream->AwaitEvent();
|
||||
// if (dynamic_cast<CommitDirective *>(m.get())) {
|
||||
// masterChannel->Send<CountNodesTxnResult>(num);
|
||||
// } else if (dynamic_cast<AbortDirective *>(m.get())) {
|
||||
// // send nothing
|
||||
// } else {
|
||||
// std::cerr << "unknown message\n";
|
||||
// exit(1);
|
||||
// }
|
||||
// CloseChannel(GetTxnChannelName(txn->id()));
|
||||
// }
|
||||
|
||||
// // TODO: Don't repeat code from Master.
|
||||
// std::string GetTxnChannelName(int64_t transaction_id) {
|
||||
// return "txn" + std::to_string(transaction_id);
|
||||
// }
|
||||
|
||||
// void FindMaster() {
|
||||
// // TODO: Replace with network service and channel resolution.
|
||||
// while (!(master_channel_ = system_->FindChannel("master", "main")))
|
||||
// std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
// }
|
||||
|
||||
// std::shared_ptr<Channel> master_channel_ = nullptr;
|
||||
// int worker_id_;
|
||||
// };
|
||||
|
||||
// void ClientMain(System *system) {
|
||||
// std::shared_ptr<Channel> channel = nullptr;
|
||||
// // TODO: Replace this with network channel resolution.
|
||||
// while (!(channel = system->FindChannel("master", "main")))
|
||||
// std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
// std::cout << "I/O Client Main active" << std::endl;
|
||||
|
||||
// bool active = true;
|
||||
// while (active) {
|
||||
// std::string s;
|
||||
// std::getline(std::cin, s);
|
||||
// if (s == "quit") {
|
||||
// active = false;
|
||||
// channel->Send<Quit>();
|
||||
// } else {
|
||||
// channel->Send<Query>(s);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
int main(int, char **) { return 0; }
|
||||
// int main(int argc, char *argv[]) {
|
||||
// //google::InitGoogleLogging(argv[0]);
|
||||
// gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
// System system;
|
||||
// system.Spawn<Master>("master");
|
||||
// std::thread client(ClientMain, &system);
|
||||
// for (int i = 0; i < NUM_WORKERS; ++i)
|
||||
// system.Spawn<Worker>("worker" + std::to_string(i), i);
|
||||
// system.AwaitShutdown();
|
||||
// return 0;
|
||||
// }
|
||||
@@ -1,102 +0,0 @@
|
||||
// command to run:
|
||||
// gnome-terminal --tab -e './network_chat --port 10000 --minloglevel 2' --tab -e './network_chat --port 10001 --minloglevel 2'
|
||||
|
||||
#include "reactors_distributed.hpp"
|
||||
|
||||
class ChatMessage : public ReturnAddressMsg {
|
||||
public:
|
||||
ChatMessage() : ReturnAddressMsg(), message_("") {}
|
||||
|
||||
ChatMessage(std::string reactor, std::string channel, std::string message)
|
||||
: ReturnAddressMsg(reactor, channel), message_(message) {}
|
||||
|
||||
std::string Message() const { return message_; }
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive &ar) {
|
||||
ar(cereal::base_class<ReturnAddressMsg>(this), message_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string message_;
|
||||
};
|
||||
CEREAL_REGISTER_TYPE(ChatMessage);
|
||||
|
||||
class ChatACK : public ChatMessage {
|
||||
public:
|
||||
ChatACK() : ChatMessage() {}
|
||||
|
||||
ChatACK(std::string reactor, std::string channel, std::string message)
|
||||
: ChatMessage(reactor, channel, message) {}
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive &ar) {
|
||||
ar(cereal::base_class<ChatMessage>(this));
|
||||
}
|
||||
};
|
||||
CEREAL_REGISTER_TYPE(ChatACK);
|
||||
|
||||
class ChatServer : public Reactor {
|
||||
public:
|
||||
ChatServer(std::string name)
|
||||
: Reactor(name) {}
|
||||
|
||||
virtual void Run() {
|
||||
std::cout << "ChatServer is active" << std::endl;
|
||||
|
||||
auto chat = Open("chat").first;
|
||||
|
||||
chat->OnEvent<ChatACK>([](const ChatACK& ack, const Subscription&) {
|
||||
std::cout << "Received ACK from " << ack.Address() << ":"
|
||||
<< ack.Port() << " -> '" << ack.Message() << "'"
|
||||
<< std::endl;
|
||||
});
|
||||
|
||||
chat->OnEvent<ChatMessage>([this](const ChatMessage& msg, const Subscription&) {
|
||||
std::cout << "Received message from " << msg.Address() << ":"
|
||||
<< msg.Port() << " -> '" << msg.Message() << "'"
|
||||
<< std::endl;
|
||||
auto channel = msg.GetReturnChannelWriter();
|
||||
if (channel != nullptr) {
|
||||
channel->Send<ChatACK>("server", "chat", msg.Message());
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class ChatClient : public Reactor {
|
||||
public:
|
||||
ChatClient(std::string name)
|
||||
: Reactor(name) {}
|
||||
|
||||
virtual void Run() {
|
||||
std::cout << "ChatClient is active" << std::endl;
|
||||
|
||||
std::string address, message;
|
||||
uint16_t port;
|
||||
while (true) {
|
||||
std::cout << "Enter IP, port and message to send." << std::endl;
|
||||
std::cin >> address >> port >> message;
|
||||
|
||||
auto channel =
|
||||
Distributed::GetInstance().network().Resolve(address, port, "server", "chat");
|
||||
if (channel != nullptr) {
|
||||
channel->Send<ChatMessage>("server", "chat", message);
|
||||
} else {
|
||||
std::cerr << "Couldn't resolve that server!" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
System& system = System::GetInstance();
|
||||
Distributed &distributed = Distributed::GetInstance();
|
||||
distributed.StartServices();
|
||||
system.Spawn<ChatServer>("server");
|
||||
system.Spawn<ChatClient>("client");
|
||||
system.AwaitShutdown();
|
||||
distributed.StopServices();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
#include "reactors_distributed.hpp"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
google::InitGoogleLogging(argv[0]);
|
||||
Distributed &distributed = Distributed::GetInstance();
|
||||
distributed.network().StartClient(1);
|
||||
auto channel = distributed.network().Resolve("127.0.0.1", 10000, "master", "main");
|
||||
std::cout << channel << std::endl;
|
||||
if (channel != nullptr) {
|
||||
channel->Send<ReturnAddressMsg>("master", "main");
|
||||
}
|
||||
distributed.network().StopClient();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "graph.hpp"
|
||||
#include "spinner.hpp"
|
||||
|
||||
void PrintStatistics(const Distributed &distributed) {
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
for (const ShardedStorage &mnode : distributed) {
|
||||
cout << " ShardedStorage " << mnode.mnid_ << ":";
|
||||
cout << " #vertices = " << mnode.VertexCount();
|
||||
int64_t edge_count{0};
|
||||
for (const auto &gid_vertex_pair : mnode) {
|
||||
edge_count += gid_vertex_pair.second.edges_in().size();
|
||||
edge_count += gid_vertex_pair.second.edges_out().size();
|
||||
}
|
||||
cout << ", #edges = " << edge_count;
|
||||
cout << ", #cuts = " << mnode.BoundaryEdgeCount() << endl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an undirected graph from file.
|
||||
* - first line of the file: vertices_count, edges_count
|
||||
* - next edges_count lines contain vertices that form an edge
|
||||
* example:
|
||||
* https://snap.stanford.edu/data/facebook_combined.txt.gz
|
||||
* add number of vertices and edges in the first line of that file
|
||||
*/
|
||||
Distributed ReadGraph(std::string filename, int mnode_count) {
|
||||
Distributed distributed(mnode_count);
|
||||
|
||||
std::fstream fs;
|
||||
fs.open(filename, std::fstream::in);
|
||||
if (fs.fail()) return distributed;
|
||||
|
||||
int vertex_count, edge_count;
|
||||
fs >> vertex_count >> edge_count;
|
||||
|
||||
// assign vertices to random mnodes
|
||||
std::vector<GlobalVertAddress> vertices;
|
||||
for (int i = 0; i < vertex_count; ++i)
|
||||
vertices.emplace_back(distributed.MakeVertex(rand() % mnode_count));
|
||||
|
||||
// add edges
|
||||
for (int i = 0; i < edge_count; ++i) {
|
||||
size_t u, v;
|
||||
fs >> u >> v;
|
||||
assert(u < vertices.size() && v < vertices.size());
|
||||
distributed.MakeEdge(vertices[u], vertices[v]);
|
||||
}
|
||||
fs.close();
|
||||
return distributed;
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
srand(time(NULL));
|
||||
|
||||
if (argc == 1) {
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout << argv[0] << " filename partitions iterations" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::cout << "Memgraph spinner test " << std::endl;
|
||||
std::string filename(argv[1]);
|
||||
int partitions = std::max(1, atoi(argv[2]));
|
||||
int iterations = std::max(1, atoi(argv[3]));
|
||||
|
||||
Distributed distributed = ReadGraph(filename, partitions);
|
||||
PrintStatistics(distributed);
|
||||
for (int iter = 0; iter < iterations; ++iter) {
|
||||
spinner::PerformSpinnerStep(distributed);
|
||||
std::cout << "Iteration " << iter << std::endl;
|
||||
PrintStatistics(distributed);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user