You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hugegraph.apache.org by zh...@apache.org on 2025/01/15 01:14:24 UTC
(incubator-hugegraph-computer) branch master updated: Adding support for Vermeer functionality and removing invalid repositories (#316)
This is an automated email from the ASF dual-hosted git repository.
zhaocong pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-hugegraph-computer.git
The following commit(s) were added to refs/heads/master by this push:
new e575fd91 Adding support for Vermeer functionality and removing invalid repositories (#316)
e575fd91 is described below
commit e575fd91897b58bc894172731232d573833d4a14
Author: Skittles258 <16...@users.noreply.github.com>
AuthorDate: Wed Jan 15 09:14:19 2025 +0800
Adding support for Vermeer functionality and removing invalid repositories (#316)
---
README.md | 1 +
vermeer/.dockerignore | 19 +
vermeer/.gitignore | 85 +
vermeer/Dockerfile | 32 +
vermeer/README.md | 57 +
vermeer/algorithms/algorithms.go | 22 +
vermeer/algorithms/betweenness_centrality.go | 246 +
vermeer/algorithms/closeness_centrality.go | 196 +
vermeer/algorithms/clustering_coeffcient.go | 142 +
vermeer/algorithms/cycle_detection.go | 276 +
vermeer/algorithms/degree.go | 115 +
vermeer/algorithms/depth.go | 199 +
vermeer/algorithms/jaccard.go | 145 +
vermeer/algorithms/kcore.go | 144 +
vermeer/algorithms/kout.go | 199 +
vermeer/algorithms/louvain.go | 710 +++
vermeer/algorithms/louvain_weighted.go | 748 +++
vermeer/algorithms/lpa.go | 286 +
vermeer/algorithms/pagerank.go | 192 +
vermeer/algorithms/personalized_pagerank.go | 154 +
vermeer/algorithms/scc.go | 248 +
vermeer/algorithms/slpa.go | 251 +
vermeer/algorithms/sssp.go | 125 +
vermeer/algorithms/triangle_count.go | 150 +
vermeer/algorithms/wcc.go | 129 +
vermeer/apps/auth/filter.go | 184 +
vermeer/apps/auth/token.go | 119 +
vermeer/apps/bsp/bsp.go | 18 +
vermeer/apps/bsp/bsp_consts.go | 18 +
vermeer/apps/buffer/double_buffer.go | 58 +
vermeer/apps/buffer/encode_buffer.go | 91 +
vermeer/apps/common/base_handler.go | 185 +
vermeer/apps/common/configer.go | 66 +
vermeer/apps/common/consts.go | 27 +
vermeer/apps/common/exp_parser.go | 156 +
vermeer/apps/common/hash_util.go | 26 +
vermeer/apps/common/heap_sort.go | 49 +
vermeer/apps/common/http_server.go | 108 +
vermeer/apps/common/hugegraph_tools.go | 345 +
vermeer/apps/common/locker.go | 68 +
vermeer/apps/common/log_formatter.go | 35 +
vermeer/apps/common/metrics.go | 168 +
vermeer/apps/common/os_util.go | 66 +
vermeer/apps/common/panic_stack.go | 31 +
vermeer/apps/common/queue.go | 134 +
vermeer/apps/common/string_util.go | 57 +
vermeer/apps/compute/api.go | 560 ++
vermeer/apps/compute/filter.go | 180 +
vermeer/apps/compute/statistics.go | 615 ++
vermeer/apps/compute/task.go | 199 +
vermeer/apps/graphio/hdfs_file.go | 371 ++
vermeer/apps/graphio/hugegraph.go | 740 +++
vermeer/apps/graphio/load_graph.go | 488 ++
vermeer/apps/graphio/local_file.go | 299 +
vermeer/apps/master/access/access_manager.go | 82 +
vermeer/apps/master/bl/admin_bl.go | 93 +
vermeer/apps/master/bl/bl.go | 46 +
vermeer/apps/master/bl/compute_task.go | 336 +
vermeer/apps/master/bl/grpc_handlers.go | 529 ++
vermeer/apps/master/bl/load_task.go | 281 +
vermeer/apps/master/bl/scheduler_bl.go | 278 +
vermeer/apps/master/bl/server_manager.go | 96 +
vermeer/apps/master/bl/task_bl.go | 303 +
vermeer/apps/master/bl/task_canceler.go | 232 +
vermeer/apps/master/bl/task_creator.go | 182 +
vermeer/apps/master/bl/task_starter.go | 325 +
vermeer/apps/master/bl/worker_bl.go | 97 +
vermeer/apps/master/graphs/edges_bl.go | 30 +
vermeer/apps/master/graphs/graph_bl.go | 439 ++
vermeer/apps/master/graphs/graph_persistence.go | 181 +
vermeer/apps/master/graphs/graphs.go | 42 +
vermeer/apps/master/master_main.go | 112 +
vermeer/apps/master/schedules/broker.go | 262 +
vermeer/apps/master/schedules/schedules.go | 26 +
vermeer/apps/master/schedules/space_queue.go | 162 +
vermeer/apps/master/schedules/task_queue.go | 139 +
vermeer/apps/master/services/http_admin.go | 449 ++
vermeer/apps/master/services/http_graphs.go | 196 +
vermeer/apps/master/services/http_login.go | 69 +
vermeer/apps/master/services/http_master.go | 35 +
vermeer/apps/master/services/http_oltp.go | 55 +
vermeer/apps/master/services/http_task.go | 147 +
vermeer/apps/master/services/http_tasks.go | 233 +
vermeer/apps/master/services/http_ui.go | 34 +
vermeer/apps/master/services/http_util.go | 95 +
vermeer/apps/master/services/http_workers.go | 45 +
vermeer/apps/master/services/router.go | 123 +
vermeer/apps/master/services/service.go | 163 +
vermeer/apps/master/store/service_store.go | 69 +
vermeer/apps/master/tasks/task_util.go | 133 +
vermeer/apps/master/tasks/tasks.go | 23 +
vermeer/apps/master/threshold/memory.go | 131 +
vermeer/apps/master/threshold/store.go | 226 +
vermeer/apps/master/threshold/threshold.go | 24 +
vermeer/apps/master/workers/worker_manager.go | 772 +++
vermeer/apps/master/workers/worker_util.go | 115 +
vermeer/apps/master/workers/workers.go | 76 +
vermeer/apps/options/option.go | 306 +
vermeer/apps/protos/common.pb.go | 220 +
vermeer/apps/protos/common.proto | 28 +
vermeer/apps/protos/discovery.proto | 72 +
vermeer/apps/protos/graphpb.proto | 140 +
.../hugegraph-pd-grpc/common/pd_common.pb.go | 405 ++
.../apps/protos/hugegraph-pd-grpc/discovery.pb.go | 633 ++
.../protos/hugegraph-pd-grpc/discovery_grpc.pb.go | 157 +
vermeer/apps/protos/hugegraph-pd-grpc/kv/kv.pb.go | 1529 +++++
.../apps/protos/hugegraph-pd-grpc/kv/kv_grpc.pb.go | 644 ++
.../hugegraph-pd-grpc/metaTask/metaTask.pb.go | 521 ++
.../protos/hugegraph-pd-grpc/metapb/metapb.pb.go | 3519 ++++++++++
vermeer/apps/protos/hugegraph-pd-grpc/pdpb.pb.go | 6735 ++++++++++++++++++++
.../apps/protos/hugegraph-pd-grpc/pdpb_grpc.pb.go | 1615 +++++
.../protos/hugegraph-pd-grpc/pulse/pd_pulse.pb.go | 1685 +++++
.../hugegraph-pd-grpc/pulse/pd_pulse_grpc.pb.go | 154 +
.../hugegraph-store-grpc/common/store_common.pb.go | 1082 ++++
.../apps/protos/hugegraph-store-grpc/graphpb.pb.go | 1398 ++++
.../protos/hugegraph-store-grpc/graphpb_grpc.pb.go | 153 +
vermeer/apps/protos/kv.proto | 144 +
vermeer/apps/protos/make_proto.bat | 19 +
vermeer/apps/protos/master.pb.go | 3245 ++++++++++
vermeer/apps/protos/master.proto | 266 +
vermeer/apps/protos/master_grpc.pb.go | 717 +++
vermeer/apps/protos/metaTask.proto | 65 +
vermeer/apps/protos/metapb.proto | 392 ++
vermeer/apps/protos/pd_common.proto | 54 +
vermeer/apps/protos/pd_pulse.proto | 162 +
vermeer/apps/protos/pdpb.proto | 582 ++
vermeer/apps/protos/store_common.proto | 113 +
vermeer/apps/protos/worker.pb.go | 3336 ++++++++++
vermeer/apps/protos/worker.proto | 276 +
vermeer/apps/protos/worker_grpc.pb.go | 718 +++
vermeer/apps/serialize/basic_types.go | 195 +
vermeer/apps/serialize/encode_buffer.go | 83 +
vermeer/apps/serialize/map.go | 419 ++
vermeer/apps/serialize/serialize.go | 64 +
vermeer/apps/serialize/slice.go | 799 +++
vermeer/apps/serialize/user_type.go | 61 +
vermeer/apps/storage/leveldb.go | 198 +
vermeer/apps/storage/pebble.go | 241 +
vermeer/apps/storage/store.go | 92 +
vermeer/apps/structure/credential.go | 82 +
vermeer/apps/structure/edge.go | 216 +
vermeer/apps/structure/graph.go | 409 ++
vermeer/apps/structure/graph_data.go | 208 +
vermeer/apps/structure/graph_manager.go | 507 ++
vermeer/apps/structure/graph_meta.go | 959 +++
vermeer/apps/structure/graph_test.go | 454 ++
vermeer/apps/structure/locker.go | 52 +
vermeer/apps/structure/property.go | 950 +++
vermeer/apps/structure/property_test.go | 134 +
vermeer/apps/structure/space.go | 32 +
vermeer/apps/structure/space_manager.go | 65 +
vermeer/apps/structure/state.go | 124 +
vermeer/apps/structure/task.go | 565 ++
vermeer/apps/structure/user.go | 38 +
vermeer/apps/structure/vertex.go | 770 +++
vermeer/apps/task/task_manager.go | 18 +
vermeer/apps/version/version.go | 20 +
vermeer/apps/worker/compute_bl.go | 785 +++
vermeer/apps/worker/grpc_handlers.go | 818 +++
vermeer/apps/worker/http_handler.go | 171 +
vermeer/apps/worker/load_graph_bl.go | 1489 +++++
vermeer/apps/worker/peer_manager.go | 105 +
vermeer/apps/worker/router.go | 39 +
vermeer/apps/worker/runtime_supervisor.go | 167 +
vermeer/apps/worker/service.go | 382 ++
vermeer/apps/worker/setting_bl.go | 614 ++
vermeer/apps/worker/worker_main.go | 86 +
vermeer/asset/asset.go | 28 +
vermeer/asset/asset_dev_ui.go | 31 +
vermeer/asset/asset_generate.go | 40 +
vermeer/asset/assets_vfsdata.go | 609 ++
vermeer/asset/vfsgen.go | 25 +
vermeer/build.sh | 39 +
vermeer/build_docker.sh | 34 +
vermeer/client/client.go | 479 ++
vermeer/client/structure.go | 174 +
vermeer/config/default.ini | 23 +
vermeer/config/master.ini | 28 +
vermeer/config/supervisor.conf | 52 +
vermeer/config/worker.ini | 24 +
vermeer/config/worker01.ini | 23 +
vermeer/config/worker02.ini | 24 +
vermeer/config/worker03.ini | 24 +
vermeer/config/worker04.ini | 24 +
vermeer/config/worker05.ini | 24 +
vermeer/config/worker06.ini | 24 +
vermeer/config/worker07.ini | 24 +
vermeer/config/worker08.ini | 24 +
vermeer/config/worker09.ini | 23 +
vermeer/gitattributes | 93 +
vermeer/go.mod | 86 +
vermeer/go.sum | 308 +
vermeer/main.go | 220 +
vermeer/mem_supervisor.sh | 40 +
vermeer/plugin_src/pagerank.go | 153 +
vermeer/test/functional/compute_base.go | 281 +
.../functional/compute_betweenness_centrality.go | 101 +
.../functional/compute_closeness_centrality.go | 105 +
.../functional/compute_clustering_coefficient.go | 36 +
vermeer/test/functional/compute_degree.go | 139 +
vermeer/test/functional/compute_jaccard.go | 38 +
vermeer/test/functional/compute_kcore.go | 37 +
vermeer/test/functional/compute_kout_all.go | 36 +
vermeer/test/functional/compute_louvain.go | 50 +
vermeer/test/functional/compute_lpa.go | 109 +
vermeer/test/functional/compute_pagerank.go | 38 +
vermeer/test/functional/compute_ppr.go | 39 +
vermeer/test/functional/compute_scc.go | 84 +
vermeer/test/functional/compute_sssp.go | 37 +
vermeer/test/functional/compute_task.go | 126 +
vermeer/test/functional/compute_trianglecount.go | 36 +
vermeer/test/functional/compute_wcc.go | 85 +
vermeer/test/functional/expect_res.go | 48 +
vermeer/test/functional/healthcheck.go | 44 +
vermeer/test/functional/http_interface.go | 108 +
vermeer/test/functional/load_afs.go | 42 +
vermeer/test/functional/load_hugegraph.go | 42 +
vermeer/test/functional/load_local.go | 45 +
vermeer/test/functional/load_task.go | 152 +
vermeer/test/functional/test_function.go | 333 +
.../linux64/include/google/protobuf/any.proto | 175 +
.../linux64/include/google/protobuf/api.proto | 225 +
.../include/google/protobuf/compiler/plugin.proto | 200 +
.../include/google/protobuf/descriptor.proto | 938 +++
.../linux64/include/google/protobuf/duration.proto | 133 +
.../linux64/include/google/protobuf/empty.proto | 68 +
.../include/google/protobuf/field_mask.proto | 262 +
.../include/google/protobuf/source_context.proto | 65 +
.../linux64/include/google/protobuf/struct.proto | 112 +
.../include/google/protobuf/timestamp.proto | 164 +
.../linux64/include/google/protobuf/type.proto | 204 +
.../linux64/include/google/protobuf/wrappers.proto | 140 +
vermeer/tools/protoc/linux64/protoc | Bin 0 -> 4648136 bytes
.../protoc/osxm1/include/google/protobuf/any.proto | 175 +
.../protoc/osxm1/include/google/protobuf/api.proto | 225 +
.../include/google/protobuf/compiler/plugin.proto | 200 +
.../osxm1/include/google/protobuf/descriptor.proto | 938 +++
.../osxm1/include/google/protobuf/duration.proto | 133 +
.../osxm1/include/google/protobuf/empty.proto | 68 +
.../osxm1/include/google/protobuf/field_mask.proto | 262 +
.../include/google/protobuf/source_context.proto | 65 +
.../osxm1/include/google/protobuf/struct.proto | 112 +
.../osxm1/include/google/protobuf/timestamp.proto | 164 +
.../osxm1/include/google/protobuf/type.proto | 204 +
.../osxm1/include/google/protobuf/wrappers.proto | 140 +
vermeer/tools/protoc/osxm1/protoc | Bin 0 -> 3938995 bytes
.../protoc/win64/include/google/protobuf/any.proto | 175 +
.../protoc/win64/include/google/protobuf/api.proto | 225 +
.../include/google/protobuf/compiler/plugin.proto | 200 +
.../win64/include/google/protobuf/descriptor.proto | 938 +++
.../win64/include/google/protobuf/duration.proto | 133 +
.../win64/include/google/protobuf/empty.proto | 68 +
.../win64/include/google/protobuf/field_mask.proto | 262 +
.../include/google/protobuf/source_context.proto | 65 +
.../win64/include/google/protobuf/struct.proto | 112 +
.../win64/include/google/protobuf/timestamp.proto | 164 +
.../win64/include/google/protobuf/type.proto | 204 +
.../win64/include/google/protobuf/wrappers.proto | 140 +
vermeer/tools/supervisord/linux_amd64/supervisord | Bin 0 -> 3800096 bytes
vermeer/tools/supervisord/linux_arm64/supervisord | Bin 0 -> 3452480 bytes
vermeer/tools/token/token_generator.go | 59 +
vermeer/vermeer.sh | 202 +
vermeer/vermeer_test.go | 203 +
vermeer/vermeer_test.sh | 19 +
264 files changed, 74746 insertions(+)
diff --git a/README.md b/README.md
index 23cd87e2..4d37a127 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,7 @@ The [hugegraph-computer](./computer/README.md) is a distributed graph processing
The [project homepage](https://hugegraph.apache.org/docs/quickstart/hugegraph-computer/) contains more information about hugegraph-computer.
And here are links of other repositories:
+
1. [hugegraph](https://github.com/apache/hugegraph) (graph's core component - Graph server + PD + Store)
2. [hugegraph-toolchain](https://github.com/apache/hugegraph-toolchain) (graph tools **[loader](https://github.com/apache/incubator-hugegraph-toolchain/tree/master/hugegraph-loader)/[dashboard](https://github.com/apache/incubator-hugegraph-toolchain/tree/master/hugegraph-hubble)/[tool](https://github.com/apache/incubator-hugegraph-toolchain/tree/master/hugegraph-tools)/[client](https://github.com/apache/incubator-hugegraph-toolchain/tree/master/hugegraph-client)**)
3. [hugegraph-ai](https://github.com/apache/incubator-hugegraph-ai) (integrated **Graph AI/LLM/KG** system)
diff --git a/vermeer/.dockerignore b/vermeer/.dockerignore
new file mode 100644
index 00000000..6e8f887b
--- /dev/null
+++ b/vermeer/.dockerignore
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+*data*
+*test*
+*.git*
\ No newline at end of file
diff --git a/vermeer/.gitignore b/vermeer/.gitignore
new file mode 100644
index 00000000..540a67ae
--- /dev/null
+++ b/vermeer/.gitignore
@@ -0,0 +1,85 @@
+# Golang #
+######################
+# `go test -c` 生成的二进制文件
+*.test
+
+# go coverage 工具
+*.out
+*.prof
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+# 编译文件 #
+###################
+*.com
+*.class
+#*.dll
+*.exe
+#*.o
+#*.so
+main
+vermeer
+
+# 压缩包 #
+############
+# Git 自带压缩,如果这些压缩包里有代码,建议解压后 commit
+*.7z
+*.dmg
+*.gz
+*.iso
+*.jar
+*.rar
+*.tar
+*.zip
+
+# 日志文件和数据库 #
+######################
+*.log
+*.sqlite
+*.db
+
+# 临时文件 #
+######################
+tmp/
+.tmp/
+data/
+result/
+vermeer_data/
+
+# 系统生成文件 #
+######################
+.DS_Store
+.DS_Store?
+.AppleDouble
+.LSOverride
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+.TemporaryItems
+.fseventsd
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# IDE 和编辑器 #
+######################
+.idea/
+/go_build_*
+out/
+#.vscode/
+.vscode/settings.json
+*.sublime*
+__debug_bin
+.project
+
+# 前端工具链 #
+######################
+.sass-cache/*
+node_modules/
+/output/
+/bin/*
+!/bin/*.sh
diff --git a/vermeer/Dockerfile b/vermeer/Dockerfile
new file mode 100644
index 00000000..efe365bc
--- /dev/null
+++ b/vermeer/Dockerfile
@@ -0,0 +1,32 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+FROM golang:1.18-alpine AS builder
+COPY ./ /src/
+WORKDIR /src/
+ENV CGO_ENABLED="0"
+RUN go build -o /go/bin/app
+
+FROM alpine
+EXPOSE 8080
+COPY --from=builder /go/bin/app /go/bin/app
+COPY --from=builder /src/config/ /go/bin/config/
+COPY --from=builder /usr/local/go/lib/time/zoneinfo.zip /
+ENV TZ=Asia/Shanghai
+ENV ZONEINFO=/zoneinfo.zip
+
+WORKDIR /go/bin/
+ENTRYPOINT ["/go/bin/app"]
diff --git a/vermeer/README.md b/vermeer/README.md
new file mode 100644
index 00000000..f2f0fc78
--- /dev/null
+++ b/vermeer/README.md
@@ -0,0 +1,57 @@
+# 图计算平台
+
+### grpc protobuf 依赖项安装
+
+````
+go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.0 \
+go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2.0
+````
+
+---
+
+### protobuf build
+
+````
+../../tools/protoc/osxm1/protoc *.proto --go-grpc_out=. --go_out=.
+````
+
+---
+
+### 交叉编译
+
+````
+linux: GOARCH=amd64 GOOS=linux go build
+CC=x86_64-linux-musl-gcc CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -buildmode=plugin
+````
+
+---
+
+### 运行
+
+```
+master: ./vermeer --env=master
+worker: ./vermeer --env=worker01
+# 参数env是指定使用config文件夹下的配置文件名
+or
+./vermeer.sh start master
+./vermeer.sh start worker
+# 配置项在vermeer.sh中指定
+```
+
+---
+
+## supervisord
+
+配置文件参考 config/supervisor.conf
+
+````
+# 启动 run as daemon
+./supervisord -c supervisor.conf -d
+````
+
+# 使用hubble平台
+有三种搭建方式,参考https://hugegraph.apache.org/docs/quickstart/hugegraph-hubble/
+
+Use Docker (Convenient for Test/Dev)
+Download the Toolchain binary package
+Source code compilation
diff --git a/vermeer/algorithms/algorithms.go b/vermeer/algorithms/algorithms.go
new file mode 100644
index 00000000..5951afa0
--- /dev/null
+++ b/vermeer/algorithms/algorithms.go
@@ -0,0 +1,22 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import "vermeer/apps/compute"
+
+var Algorithms []compute.AlgorithmMaker
diff --git a/vermeer/algorithms/betweenness_centrality.go b/vermeer/algorithms/betweenness_centrality.go
new file mode 100644
index 00000000..f8b28535
--- /dev/null
+++ b/vermeer/algorithms/betweenness_centrality.go
@@ -0,0 +1,246 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import (
+ "fmt"
+ "math/rand"
+ "vermeer/apps/common"
+ "vermeer/apps/compute"
+ "vermeer/apps/options"
+ "vermeer/apps/serialize"
+
+ "github.com/sirupsen/logrus"
+)
+
+func init() {
+ Algorithms = append(Algorithms, new(BetweennessCentralityMaker))
+}
+
+type BetweennessCentralityMaker struct {
+ compute.AlgorithmMakerBase
+}
+
+func (bc *BetweennessCentralityMaker) CreateWorkerComputer() compute.WorkerComputer {
+ return &BetweennessCentralityWorker{}
+}
+
+func (bc *BetweennessCentralityMaker) CreateMasterComputer() compute.MasterComputer {
+ return &BetweennessCentralityMaster{}
+}
+
+func (bc *BetweennessCentralityMaker) Name() string {
+ return "betweenness_centrality"
+}
+
+func (bc *BetweennessCentralityMaker) DataNeeded() []string {
+ return []string{}
+}
+
+type BetweennessCentralityWorker struct {
+ compute.WorkerComputerBase
+ useEndPoint bool
+ stopFlag bool
+ scale serialize.SFloat32
+ sampleRate float64
+ stopSum []serialize.SInt32
+ betweennessValues []serialize.SFloat32
+ tempEdges [][]serialize.SUint32
+ neighborEdges []serialize.SliceUint32
+ betweennessValueMap []serialize.MapUint32Float32
+ bfsNewValues []map[uint32][][]uint32
+ bfsOldValues []map[uint32][][]uint32
+}
+
+func (bcw *BetweennessCentralityWorker) VertexValue(i uint32) serialize.MarshalAble {
+ if bcw.WContext.Step == 1 {
+ return &bcw.neighborEdges[i]
+ }
+ if bcw.stopFlag && bcw.WContext.Step > 1 {
+ return &bcw.betweennessValues[i]
+ }
+ return &bcw.betweennessValueMap[i]
+}
+
+func (bcw *BetweennessCentralityWorker) Init() error {
+ bcw.betweennessValues = make([]serialize.SFloat32, bcw.WContext.GraphData.Vertex.TotalVertexCount())
+ bcw.neighborEdges = make([]serialize.SliceUint32, bcw.WContext.GraphData.Vertex.TotalVertexCount())
+ bcw.betweennessValueMap = make([]serialize.MapUint32Float32, bcw.WContext.GraphData.Vertex.TotalVertexCount())
+ for i := range bcw.betweennessValueMap {
+ bcw.betweennessValueMap[i] = make(map[serialize.SUint32]serialize.SFloat32)
+ }
+ bcw.tempEdges = make([][]serialize.SUint32, bcw.WContext.Parallel)
+ for i := range bcw.tempEdges {
+ bcw.tempEdges[i] = make([]serialize.SUint32, 0, 100)
+ }
+ bcw.bfsNewValues = make([]map[uint32][][]uint32, bcw.WContext.GraphData.VertexCount)
+ bcw.bfsOldValues = make([]map[uint32][][]uint32, bcw.WContext.GraphData.VertexCount)
+ bcw.stopSum = make([]serialize.SInt32, bcw.WContext.Parallel)
+ bcw.sampleRate = options.GetFloat(bcw.WContext.Params, "betweenness_centrality.sample_rate")
+ if bcw.sampleRate > 1.0 || bcw.sampleRate <= 0 {
+ logrus.Errorf("The param betweenness_centrality.sample_rate must be in (0.0, 1.0], actual got '%v",
+ bcw.sampleRate)
+ return fmt.Errorf("the param betweenness_centrality.sample_rate must be in (0.0, 1.0], actual got '%v",
+ bcw.sampleRate)
+ }
+ bcw.useEndPoint = false
+ if options.GetInt(bcw.WContext.Params, "betweenness_centrality.use_endpoint") == 1 {
+ bcw.useEndPoint = true
+ }
+ bcw.stopFlag = false
+
+ n := serialize.SFloat32(bcw.WContext.GraphData.Vertex.TotalVertexCount())
+ if n < 2 {
+ bcw.scale = 1
+ } else if bcw.useEndPoint {
+ n *= serialize.SFloat32(bcw.sampleRate)
+ bcw.scale = 1 / serialize.SFloat32(n*(n-1))
+ } else {
+ n *= serialize.SFloat32(bcw.sampleRate)
+ bcw.scale = 1 / serialize.SFloat32((n-1)*(n-2))
+ }
+
+ bcw.WContext.CreateValue("stop_sum", compute.ValueTypeInt32, compute.CValueActionAggregate)
+ bcw.WContext.SetValue("stop_sum", serialize.SInt32(0))
+ bcw.WContext.CreateValue("stop_flag", compute.ValueTypeInt32, compute.CValueActionAggregate)
+ bcw.WContext.SetValue("stop_flag", serialize.SInt32(0))
+ return nil
+}
+
+func (bcw *BetweennessCentralityWorker) BeforeStep() {
+ for i := range bcw.stopSum {
+ bcw.stopSum[i] = 0
+ }
+ if bcw.WContext.Step > 1 && bcw.WContext.GetValue("stop_flag").(serialize.SInt32) == 1 ||
+ options.GetInt(bcw.WContext.Params, "compute.max_step") == int(bcw.WContext.Step) {
+ bcw.stopFlag = true
+ }
+ for i := range bcw.bfsNewValues {
+ bcw.bfsOldValues[i] = make(map[uint32][][]uint32)
+ for k, v := range bcw.bfsNewValues[i] {
+ bcw.bfsOldValues[i][k] = v
+ }
+ bcw.bfsNewValues[i] = make(map[uint32][][]uint32)
+ }
+
+ if bcw.stopFlag {
+ rangeStart := bcw.WContext.GraphData.VertIDStart
+ rangeEnd := bcw.WContext.GraphData.VertIDStart + bcw.WContext.GraphData.VertexCount
+ for _, valueMap := range bcw.betweennessValueMap {
+ for k, v := range valueMap {
+ key := uint32(k)
+ if key >= rangeStart && key < rangeEnd {
+ bcw.betweennessValues[key] += v
+ }
+ }
+ }
+ }
+
+}
+
+func (bcw *BetweennessCentralityWorker) Compute(vertexID uint32, pID int) {
+ if bcw.stopFlag {
+ return
+ }
+ vertID := vertexID - bcw.WContext.GraphData.VertIDStart
+ switch bcw.WContext.Step {
+ case 1:
+ inEdges := bcw.WContext.GraphData.Edges.GetInEdges(vertID)
+ bcw.neighborEdges[vertexID] = make([]serialize.SUint32, 0, len(inEdges))
+ for _, edge := range inEdges {
+ if !bcw.sample() || uint32(edge) == vertexID {
+ continue
+ }
+ bcw.neighborEdges[vertexID] = append(bcw.neighborEdges[vertexID], edge)
+ bcw.bfsNewValues[vertID][uint32(edge)] = [][]uint32{}
+ bcw.betweennessValueMap[vertexID][edge] = 0
+ if bcw.useEndPoint {
+ bcw.betweennessValueMap[vertexID][edge] += 1 * bcw.scale
+ }
+ }
+ bcw.stopSum[pID] = 1
+ default:
+ for k, v := range bcw.bfsOldValues[vertID] {
+ for _, nId := range bcw.neighborEdges[k] {
+ if !bcw.sample() || nId == serialize.SUint32(vertexID) {
+ continue
+ }
+ if _, ok := bcw.betweennessValueMap[vertexID][nId]; !ok {
+ bcw.stopSum[pID] = 1
+ bcw.tempEdges[pID] = append(bcw.tempEdges[pID], nId)
+ for _, vi := range v {
+ newV := append(vi, k)
+ bcw.bfsNewValues[vertID][uint32(nId)] = append(bcw.bfsNewValues[vertID][uint32(nId)], newV)
+ }
+ if len(v) == 0 {
+ bcw.bfsNewValues[vertID][uint32(nId)] = append(bcw.bfsNewValues[vertID][uint32(nId)], []uint32{k})
+ }
+ }
+ }
+ }
+ for _, edge := range bcw.tempEdges[pID] {
+ bcw.betweennessValueMap[vertexID][edge] = 0
+ if bcw.useEndPoint {
+ bcw.betweennessValueMap[vertexID][edge] += 1 * bcw.scale
+ }
+ }
+ }
+ bcw.tempEdges[pID] = bcw.tempEdges[pID][0:0]
+ for _, values := range bcw.bfsNewValues[vertID] {
+ for _, v := range values {
+ for _, u := range v {
+ bcw.betweennessValueMap[vertexID][serialize.SUint32(u)] +=
+ 1.0 / serialize.SFloat32(len(values)) * bcw.scale
+ }
+ }
+ }
+
+}
+
+func (bcw *BetweennessCentralityWorker) AfterStep() {
+ var stopSum serialize.SInt32
+ for i := range bcw.stopSum {
+ stopSum += bcw.stopSum[i]
+ }
+ bcw.WContext.SetValue("stop_sum", stopSum)
+}
+
+func (bcw *BetweennessCentralityWorker) sample() bool {
+ return rand.Float64() < bcw.sampleRate
+}
+
+func (bcw *BetweennessCentralityWorker) OutputValueType() string {
+ return common.HgValueTypeFloat
+}
+
+type BetweennessCentralityMaster struct {
+ compute.MasterComputerBase
+}
+
+func (bcm *BetweennessCentralityMaster) Compute() bool {
+ logrus.Infof("stopFlag:%v", bcm.MContext.GetValue("stop_flag").(serialize.SInt32))
+ if bcm.MContext.GetValue("stop_flag").(serialize.SInt32) == serialize.SInt32(len(bcm.MContext.WorkerCValues)) {
+ return false
+ }
+ stopSum := bcm.MContext.GetValue("stop_sum")
+ logrus.Infof("stopSum:%v", stopSum)
+ if stopSum.(serialize.SInt32) == serialize.SInt32(0) {
+ bcm.MContext.SetValue("stop_flag", serialize.SInt32(1))
+ }
+ return true
+}
diff --git a/vermeer/algorithms/closeness_centrality.go b/vermeer/algorithms/closeness_centrality.go
new file mode 100644
index 00000000..b5742d4c
--- /dev/null
+++ b/vermeer/algorithms/closeness_centrality.go
@@ -0,0 +1,196 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import (
+ "fmt"
+ "math/rand"
+ "vermeer/apps/common"
+ "vermeer/apps/compute"
+ "vermeer/apps/options"
+ "vermeer/apps/serialize"
+
+ "github.com/sirupsen/logrus"
+)
+
+func init() {
+ Algorithms = append(Algorithms, new(ClosenessCentralityMaker))
+}
+
+//normalized closeness centrality
+
+type ClosenessCentralityMaker struct {
+ compute.AlgorithmMakerBase
+}
+
+func (cc *ClosenessCentralityMaker) CreateWorkerComputer() compute.WorkerComputer {
+ return &ClosenessCentralityWorker{}
+}
+
+func (cc *ClosenessCentralityMaker) CreateMasterComputer() compute.MasterComputer {
+ return &ClosenessCentralityMaster{}
+}
+
+func (cc *ClosenessCentralityMaker) Name() string {
+ return "closeness_centrality"
+}
+
+func (cc *ClosenessCentralityMaker) DataNeeded() []string {
+ return []string{}
+}
+
+type ClosenessCentralityWorker struct {
+ compute.WorkerComputerBase
+ wfImproved bool
+ sampleRate float64
+ stopSum []serialize.SInt32
+ closenessValues []serialize.SFloat32
+ tempEdges [][]serialize.SUint32
+ dfsOutsideEdge [][]serialize.SUint32
+ neighborEdges []serialize.SliceUint32
+ pMap []map[uint32]int32
+}
+
+func (ccw *ClosenessCentralityWorker) VertexValue(i uint32) serialize.MarshalAble {
+ if ccw.WContext.Step == 1 {
+ return &ccw.neighborEdges[i]
+ }
+
+ return &ccw.closenessValues[i]
+}
+
+func (ccw *ClosenessCentralityWorker) Init() error {
+ ccw.closenessValues = make([]serialize.SFloat32, ccw.WContext.GraphData.Vertex.TotalVertexCount())
+ ccw.neighborEdges = make([]serialize.SliceUint32, ccw.WContext.GraphData.Vertex.TotalVertexCount())
+ ccw.dfsOutsideEdge = make([][]serialize.SUint32, ccw.WContext.GraphData.VertexCount)
+ ccw.stopSum = make([]serialize.SInt32, ccw.WContext.Parallel)
+ ccw.pMap = make([]map[uint32]int32, ccw.WContext.GraphData.VertexCount)
+ for i := range ccw.pMap {
+ ccw.pMap[i] = make(map[uint32]int32, len(ccw.WContext.GraphData.Edges.GetInEdges(uint32(i))))
+ ccw.pMap[i][uint32(i)] = 0
+ }
+ ccw.tempEdges = make([][]serialize.SUint32, ccw.WContext.Parallel)
+ for i := range ccw.tempEdges {
+ ccw.tempEdges[i] = make([]serialize.SUint32, 0, 100)
+ }
+ ccw.sampleRate = options.GetFloat(ccw.WContext.Params, "closeness_centrality.sample_rate")
+ if ccw.sampleRate > 1.0 || ccw.sampleRate <= 0 {
+ logrus.Errorf("The param closeness_centrality.sample_rate must be in (0.0, 1.0], actual got '%v",
+ ccw.sampleRate)
+ return fmt.Errorf("the param closeness_centrality.sample_rate must be in (0.0, 1.0], actual got '%v",
+ ccw.sampleRate)
+ }
+ //wf_improved:bool. default true
+ // If True, scale by the fraction of nodes reachable. This gives the
+ // Wasserman and Faust improved formula. For single component graphs
+ // it is the same as the original formula.
+ ccw.wfImproved = true
+ wfImproved := options.GetInt(ccw.WContext.Params, "closeness_centrality.wf_improved")
+ if wfImproved != 1 {
+ ccw.wfImproved = false
+ }
+ ccw.WContext.CreateValue("stop_sum", compute.ValueTypeInt32, compute.CValueActionAggregate)
+ ccw.WContext.SetValue("stop_sum", serialize.SInt32(0))
+ return nil
+}
+
+func (ccw *ClosenessCentralityWorker) BeforeStep() {
+ for i := range ccw.stopSum {
+ ccw.stopSum[i] = 0
+ }
+}
+
+func (ccw *ClosenessCentralityWorker) Compute(vertexID uint32, pID int) {
+ vertID := vertexID - ccw.WContext.GraphData.VertIDStart
+ switch ccw.WContext.Step {
+ case 1:
+ inEdges := ccw.WContext.GraphData.Edges.GetInEdges(vertID)
+ ccw.neighborEdges[vertexID] = make([]serialize.SUint32, 0, len(inEdges))
+ for _, edge := range inEdges {
+ if !ccw.sample() || uint32(edge) == vertexID {
+ continue
+ }
+ ccw.neighborEdges[vertexID] = append(ccw.neighborEdges[vertexID], edge)
+ ccw.pMap[vertID][uint32(edge)] = 1
+ }
+ ccw.dfsOutsideEdge[vertID] = ccw.neighborEdges[vertexID]
+ ccw.stopSum[pID] = 1
+ default:
+ for _, value := range ccw.dfsOutsideEdge[vertID] {
+ for _, nId := range ccw.neighborEdges[value] {
+ if _, ok := ccw.pMap[vertID][uint32(nId)]; !ok {
+ if !ccw.sample() {
+ continue
+ }
+ ccw.pMap[vertID][uint32(nId)] = ccw.pMap[vertID][uint32(value)] + 1
+ ccw.tempEdges[pID] = append(ccw.tempEdges[pID], nId)
+ ccw.stopSum[pID] = 1
+ }
+ }
+ }
+ ccw.dfsOutsideEdge[vertID] = make([]serialize.SUint32, len(ccw.tempEdges[pID]))
+ copy(ccw.dfsOutsideEdge[vertID], ccw.tempEdges[pID])
+ }
+ ccw.tempEdges[pID] = ccw.tempEdges[pID][0:0]
+ var stopSum serialize.SInt32
+ for _, v := range ccw.stopSum {
+ stopSum += v
+ }
+ if stopSum == 0 || options.GetInt(ccw.WContext.Params, "compute.max_step") == int(ccw.WContext.Step) {
+ //计算closenessValues
+ var sum int32
+ for _, v := range ccw.pMap[vertID] {
+ sum += v
+ }
+ if sum > 0 && ccw.WContext.GraphData.Vertex.TotalVertexCount() > 1 {
+ ccw.closenessValues[vertexID] =
+ serialize.SFloat32(len(ccw.pMap[vertID])-1) / serialize.SFloat32(sum)
+ if ccw.wfImproved {
+ ccw.closenessValues[vertexID] *= serialize.SFloat32(len(ccw.pMap[vertID])-1) /
+ serialize.SFloat32(ccw.WContext.GraphData.Vertex.TotalVertexCount()-1)
+ }
+ ccw.closenessValues[vertexID] *= serialize.SFloat32(ccw.sampleRate)
+ }
+ }
+}
+
+func (ccw *ClosenessCentralityWorker) sample() bool {
+ return rand.Float64() < ccw.sampleRate
+}
+
+func (ccw *ClosenessCentralityWorker) AfterStep() {
+ var stopSum serialize.SInt32
+ for i := range ccw.stopSum {
+ stopSum += ccw.stopSum[i]
+ }
+ ccw.WContext.SetValue("stop_sum", stopSum)
+}
+
+func (ccw *ClosenessCentralityWorker) OutputValueType() string {
+ return common.HgValueTypeFloat
+}
+
+type ClosenessCentralityMaster struct {
+ compute.MasterComputerBase
+}
+
+func (ccm *ClosenessCentralityMaster) Compute() bool {
+ stopSum := ccm.MContext.GetValue("stop_sum")
+ logrus.Infof("stopSum:%v", stopSum)
+ return stopSum.(serialize.SInt32) != 0
+}
diff --git a/vermeer/algorithms/clustering_coeffcient.go b/vermeer/algorithms/clustering_coeffcient.go
new file mode 100644
index 00000000..25341ad9
--- /dev/null
+++ b/vermeer/algorithms/clustering_coeffcient.go
@@ -0,0 +1,142 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import (
+ "vermeer/apps/common"
+ "vermeer/apps/compute"
+ "vermeer/apps/serialize"
+)
+
+func init() {
+ Algorithms = append(Algorithms, new(ClusteringCoefficientMaker))
+}
+
+type ClusteringCoefficientMaker struct {
+ compute.AlgorithmMakerBase
+}
+
+func (cc *ClusteringCoefficientMaker) CreateWorkerComputer() compute.WorkerComputer {
+ return &ClusteringCoefficientWorker{}
+}
+
+func (cc *ClusteringCoefficientMaker) CreateMasterComputer() compute.MasterComputer {
+ return &ClusteringCoefficientMaster{}
+}
+
+func (cc *ClusteringCoefficientMaker) Name() string {
+ return "clustering_coefficient"
+}
+
+func (cc *ClusteringCoefficientMaker) DataNeeded() []string {
+ return []string{compute.UseOutEdge}
+}
+
+type ClusteringCoefficientWorker struct {
+ compute.WorkerComputerBase
+ ccValues []serialize.SFloat32
+ totalNeighborSet []serialize.MapUint32Struct
+ selfNeighborSet []map[serialize.SUint32]struct{}
+}
+
+func (ccw *ClusteringCoefficientWorker) VertexValue(i uint32) serialize.MarshalAble {
+ if ccw.WContext.Step == 1 {
+ return &ccw.totalNeighborSet[i]
+ }
+ return &ccw.ccValues[i]
+}
+
+func (ccw *ClusteringCoefficientWorker) Init() error {
+ ccw.ccValues = make([]serialize.SFloat32, ccw.WContext.GraphData.Vertex.TotalVertexCount())
+ ccw.totalNeighborSet = make([]serialize.MapUint32Struct, ccw.WContext.GraphData.Vertex.TotalVertexCount())
+ ccw.selfNeighborSet = make([]map[serialize.SUint32]struct{}, ccw.WContext.GraphData.VertexCount)
+ return nil
+}
+
+func (ccw *ClusteringCoefficientWorker) Compute(vertexID uint32, pID int) {
+ _ = pID
+ vertID := vertexID - ccw.WContext.GraphData.VertIDStart
+ switch ccw.WContext.Step {
+ case 1:
+ inEdges := ccw.WContext.GraphData.Edges.GetInEdges(vertID)
+ outEdges := ccw.WContext.GraphData.Edges.GetOutEdges(vertID)
+ ccw.selfNeighborSet[vertID] = make(map[serialize.SUint32]struct{},
+ len(inEdges)+len(outEdges))
+ ccw.totalNeighborSet[vertexID] = make(map[serialize.SUint32]struct{},
+ (len(inEdges)+len(outEdges))/2)
+ for _, nID := range inEdges {
+ if uint32(nID) == vertexID {
+ continue
+ }
+ ccw.selfNeighborSet[vertID][nID] = struct{}{}
+ if uint32(nID) < vertexID {
+ ccw.totalNeighborSet[vertexID][nID] = struct{}{}
+ }
+ }
+ for _, nID := range outEdges {
+ if uint32(nID) == vertexID {
+ continue
+ }
+ ccw.selfNeighborSet[vertID][nID] = struct{}{}
+ if uint32(nID) < vertexID {
+ ccw.totalNeighborSet[vertexID][nID] = struct{}{}
+ }
+ }
+ case 2:
+ length := len(ccw.selfNeighborSet[vertID])
+ if length > 1 {
+ var triangleCount serialize.SUint32
+ for fID := range ccw.selfNeighborSet[vertID] {
+ triangleCount += ccw.findCross(ccw.selfNeighborSet[vertID], ccw.totalNeighborSet[fID])
+ }
+ ccw.ccValues[vertexID] = 2 * serialize.SFloat32(triangleCount) /
+ serialize.SFloat32((length)*(length-1))
+ } else {
+ ccw.ccValues[vertexID] = 0
+ }
+ }
+}
+
+func (ccw *ClusteringCoefficientWorker) findCross(a map[serialize.SUint32]struct{}, b map[serialize.SUint32]struct{}) serialize.SUint32 {
+ var count serialize.SUint32 = 0
+ if len(a) > len(b) {
+ for k := range b {
+ if _, ok := a[k]; ok {
+ count++
+ }
+ }
+ } else {
+ for k := range a {
+ if _, ok := b[k]; ok {
+ count++
+ }
+ }
+ }
+ return count
+}
+func (ccw *ClusteringCoefficientWorker) OutputValueType() string {
+ return common.HgValueTypeFloat
+}
+
+type ClusteringCoefficientMaster struct {
+ compute.MasterComputerBase
+}
+
+func (ccm *ClusteringCoefficientMaster) Compute() bool {
+ return ccm.MContext.Step != 2
+}
diff --git a/vermeer/algorithms/cycle_detection.go b/vermeer/algorithms/cycle_detection.go
new file mode 100644
index 00000000..d7023a9b
--- /dev/null
+++ b/vermeer/algorithms/cycle_detection.go
@@ -0,0 +1,276 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import (
+ "fmt"
+ "vermeer/apps/common"
+ "vermeer/apps/compute"
+ "vermeer/apps/options"
+ "vermeer/apps/serialize"
+
+ "github.com/sirupsen/logrus"
+)
+
+func init() {
+ Algorithms = append(Algorithms, new(CycleDetectionMaker))
+}
+
+type CycleDetectionMaker struct {
+ compute.AlgorithmMakerBase
+}
+
+func (cd *CycleDetectionMaker) CreateWorkerComputer() compute.WorkerComputer {
+ return &CycleDetectionWorker{}
+}
+
+func (cd *CycleDetectionMaker) CreateMasterComputer() compute.MasterComputer {
+ return &CycleDetectionMaster{}
+}
+
+func (cd *CycleDetectionMaker) Name() string {
+ return "cycle_detection"
+}
+
+func (cd *CycleDetectionMaker) DataNeeded() []string {
+ return []string{}
+}
+
+type cyclesMode string
+
+const (
+ All cyclesMode = "all"
+ Limit cyclesMode = "limit"
+ Boolean cyclesMode = "boolean"
+)
+
+type CycleDetectionWorker struct {
+ compute.WorkerComputerBase
+ mode cyclesMode
+ maxLen int
+ minLen int
+ limit int
+ hasCycle []serialize.SUint8
+ inEdges []serialize.SliceUint32
+ cycleList []serialize.TwoDimSliceString
+ useVertexFilter bool
+ useEdgeFilter bool
+ vertexFilter *compute.VertexFilter
+ edgeFilter *compute.EdgeFilter
+}
+
+func (cdw *CycleDetectionWorker) Init() error {
+ cdw.inEdges = make([]serialize.SliceUint32, cdw.WContext.GraphData.Vertex.TotalVertexCount())
+ cdw.maxLen = options.GetInt(cdw.WContext.Params, "cycle.max_length")
+ cdw.minLen = options.GetInt(cdw.WContext.Params, "cycle.min_length")
+ cdw.mode = cyclesMode(options.GetString(cdw.WContext.Params, "cycle.mode"))
+ switch cdw.mode {
+ case All:
+ cdw.cycleList = make([]serialize.TwoDimSliceString, cdw.WContext.GraphData.VertexCount)
+ case Limit:
+ cdw.cycleList = make([]serialize.TwoDimSliceString, cdw.WContext.GraphData.VertexCount)
+ cdw.limit = options.GetInt(cdw.WContext.Params, "cycle.max_cycles")
+ case Boolean:
+ cdw.hasCycle = make([]serialize.SUint8, cdw.WContext.GraphData.VertexCount)
+ default:
+ return fmt.Errorf("cycle detection mode must be 'all', 'limit', 'boolean'. not: %v", cdw.mode)
+ }
+ cdw.WContext.CreateValue("cycle_num", compute.ValueTypeInt32, compute.CValueActionAggregate)
+ cdw.WContext.SetValue("cycle_num", serialize.SInt32(0))
+ vertexExprStr := options.GetString(cdw.WContext.Params, "filter.vertex_expr")
+ filterVertexProps := options.GetSliceString(cdw.WContext.Params, "filter.vertex_properties")
+ edgeExprStr := options.GetString(cdw.WContext.Params, "filter.edge_expr")
+ filterEdgeProps := options.GetSliceString(cdw.WContext.Params, "filter.edge_properties")
+ if vertexExprStr != "" && len(filterVertexProps) > 0 {
+ cdw.useVertexFilter = true
+ cdw.vertexFilter = &compute.VertexFilter{}
+ err := cdw.vertexFilter.Init(vertexExprStr, filterVertexProps, cdw.WContext.GraphData.VertexProperty)
+ if err != nil {
+ logrus.Errorf("vertex filter init error:%v", err)
+ return fmt.Errorf("vertex filter init error:%w", err)
+ }
+ }
+ if edgeExprStr != "" && len(filterEdgeProps) > 0 {
+ cdw.useEdgeFilter = true
+ cdw.edgeFilter = &compute.EdgeFilter{}
+ err := cdw.edgeFilter.Init(edgeExprStr, filterEdgeProps, cdw.WContext.GraphData.InEdgesProperty)
+ if err != nil {
+ logrus.Errorf("edge filter init error:%v", err)
+ return fmt.Errorf("edge filter init error:%w", err)
+ }
+ }
+ return nil
+}
+
+func (cdw *CycleDetectionWorker) VertexValue(i uint32) serialize.MarshalAble {
+ if cdw.WContext.Output {
+ switch cdw.mode {
+ case All, Limit:
+ return &cdw.cycleList[i-cdw.WContext.GraphData.VertIDStart]
+ case Boolean:
+ return &cdw.hasCycle[i-cdw.WContext.GraphData.VertIDStart]
+ }
+ }
+ if cdw.WContext.Step == 1 {
+ return &cdw.inEdges[i]
+ }
+ nilValue := serialize.SInt32(0)
+ return &nilValue
+}
+
+func (cdw *CycleDetectionWorker) BeforeStep() {
+}
+
+func (cdw *CycleDetectionWorker) Compute(vertexID uint32, pID int) {
+ if cdw.useVertexFilter && !cdw.vertexFilter.Filter(vertexID) {
+ return
+ }
+ _ = pID
+ vIDx := vertexID - cdw.WContext.GraphData.VertIDStart
+ if cdw.WContext.Step == 1 {
+ cdw.inEdges[vertexID] = make([]serialize.SUint32, 0, len(cdw.WContext.GraphData.Edges.GetInEdges(vIDx)))
+ tempMap := make(map[serialize.SUint32]struct{})
+ for idx, edge := range cdw.WContext.GraphData.Edges.GetInEdges(vIDx) {
+ if cdw.useVertexFilter && !cdw.vertexFilter.Filter(uint32(edge)) ||
+ cdw.useEdgeFilter && !cdw.edgeFilter.Filter(vIDx, uint32(idx)) {
+ continue
+ }
+ if _, ok := tempMap[edge]; ok || vertexID == uint32(edge) {
+ continue
+ }
+ tempMap[edge] = struct{}{}
+ cdw.inEdges[vertexID] = append(cdw.inEdges[vertexID], edge)
+ }
+ } else {
+ switch cdw.mode {
+ case All, Limit:
+ for _, edge := range cdw.inEdges[vertexID] {
+ if cdw.WContext.GraphData.Vertex.GetVertex(vertexID).ID < cdw.WContext.GraphData.Vertex.GetVertex(uint32(edge)).ID {
+ cdw.dfs(serialize.SUint32(vertexID), edge, []serialize.SUint32{edge})
+ }
+ }
+ case Boolean:
+ for _, edge := range cdw.inEdges[vertexID] {
+ cdw.dfs(serialize.SUint32(vertexID), edge, []serialize.SUint32{edge})
+ }
+ }
+ }
+}
+
+func (cdw *CycleDetectionWorker) dfs(rootID serialize.SUint32, vertexID serialize.SUint32, stack []serialize.SUint32) {
+ //logrus.Infof("root:%v, vID:%v, stack:%v", rootID, vertexID, stack)
+ if len(stack) > cdw.maxLen {
+ return
+ }
+ temp := make(map[serialize.SUint32]struct{}, len(stack))
+ for _, node := range stack {
+ if _, ok := temp[node]; ok {
+ return
+ }
+ temp[node] = struct{}{}
+ }
+
+ for _, edge := range cdw.inEdges[vertexID] {
+ if _, ok := temp[edge]; ok {
+ continue
+ }
+ switch cdw.mode {
+ case All:
+ if cdw.WContext.GraphData.Vertex.GetVertex(uint32(rootID)).ID < cdw.WContext.GraphData.Vertex.GetVertex(uint32(edge)).ID {
+ cdw.dfs(rootID, edge, append(stack, edge))
+ } else if rootID == edge && len(stack) >= cdw.minLen {
+ vIDx := uint32(rootID) - cdw.WContext.GraphData.VertIDStart
+ newList := make([]serialize.SString, 0, len(stack))
+ for _, node := range stack {
+ newList = append(newList, serialize.SString(cdw.WContext.GraphData.Vertex.GetVertex(uint32(node)).ID))
+ }
+ cdw.cycleList[vIDx] = append(cdw.cycleList[vIDx], newList)
+ }
+ case Limit:
+ vIDx := uint32(rootID) - cdw.WContext.GraphData.VertIDStart
+ if len(cdw.cycleList[vIDx]) >= cdw.limit {
+ return
+ }
+ if cdw.WContext.GraphData.Vertex.GetVertex(uint32(rootID)).ID < cdw.WContext.GraphData.Vertex.GetVertex(uint32(edge)).ID {
+ cdw.dfs(rootID, edge, append(stack, edge))
+ } else if rootID == edge && len(stack) >= cdw.minLen {
+ vIDx := uint32(rootID) - cdw.WContext.GraphData.VertIDStart
+ newList := make([]serialize.SString, 0, len(stack))
+ for _, node := range stack {
+ newList = append(newList, serialize.SString(cdw.WContext.GraphData.Vertex.GetVertex(uint32(node)).ID))
+ }
+ cdw.cycleList[vIDx] = append(cdw.cycleList[vIDx], newList)
+ if len(cdw.cycleList[vIDx]) >= cdw.limit {
+ return
+ }
+ }
+ case Boolean:
+ if cdw.hasCycle[uint32(rootID)-cdw.WContext.GraphData.VertIDStart] == 1 {
+ return
+ }
+ if rootID != edge {
+ cdw.dfs(rootID, edge, append(stack, edge))
+ } else if len(stack) >= cdw.minLen {
+ cdw.hasCycle[uint32(rootID)-cdw.WContext.GraphData.VertIDStart] = 1
+ return
+ }
+ }
+ }
+}
+
+func (cdw *CycleDetectionWorker) AfterStep() {
+ if cdw.WContext.Step > 1 {
+ cycleCount := 0
+ switch cdw.mode {
+ case All, Limit:
+ for _, list := range cdw.cycleList {
+ cycleCount += len(list)
+ }
+ case Boolean:
+ for _, has := range cdw.hasCycle {
+ if has == 1 {
+ cycleCount++
+ }
+ }
+ }
+
+ //logrus.Infof("cycle count:%v", cycleCount)
+ cdw.WContext.SetValue("cycle_num", serialize.SInt32(cycleCount))
+ }
+}
+
+func (cdw *CycleDetectionWorker) OutputValueType() string {
+ return common.HgValueTypeString
+}
+
+type CycleDetectionMaster struct {
+ compute.MasterComputerBase
+}
+
+func (cdm *CycleDetectionMaster) Init() error {
+ return nil
+}
+
+func (cdm *CycleDetectionMaster) Compute() bool {
+ if cdm.MContext.Step == 2 {
+ cycleNum := cdm.MContext.GetValue("cycle_num").(serialize.SInt32)
+ logrus.Infof("cycles num:%v", cycleNum)
+ return false
+ }
+ return true
+}
diff --git a/vermeer/algorithms/degree.go b/vermeer/algorithms/degree.go
new file mode 100644
index 00000000..dfa6a2a9
--- /dev/null
+++ b/vermeer/algorithms/degree.go
@@ -0,0 +1,115 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import (
+ "fmt"
+ "vermeer/apps/common"
+ "vermeer/apps/compute"
+ "vermeer/apps/options"
+ "vermeer/apps/serialize"
+
+ "github.com/sirupsen/logrus"
+)
+
+func init() {
+ Algorithms = append(Algorithms, new(DegreeMaker))
+}
+
+type DegreeMaker struct {
+ compute.AlgorithmMakerBase
+}
+
+func (dg *DegreeMaker) CreateWorkerComputer() compute.WorkerComputer {
+ return &DegreeWorker{}
+}
+
+func (dg *DegreeMaker) CreateMasterComputer() compute.MasterComputer {
+ return &DegreeMaster{}
+}
+
+func (dg *DegreeMaker) Name() string {
+ return "degree"
+}
+
+func (dg *DegreeMaker) DataNeeded() []string {
+ return []string{compute.UseOutDegree}
+}
+
+func (dg *DegreeMaker) SupportedStatistics() map[compute.StatisticsType]struct{} {
+ return map[compute.StatisticsType]struct{}{compute.StatisticsTypeSketchDegree: {}}
+}
+
+type DegreeWorker struct {
+ compute.WorkerComputerBase
+ direction serialize.SInt32
+ newValues []serialize.SUint32
+}
+
+func (dg *DegreeWorker) VertexValue(i uint32) serialize.MarshalAble {
+ return &dg.newValues[i]
+}
+
+func (dg *DegreeWorker) Init() error {
+ dg.newValues = make([]serialize.SUint32, dg.WContext.GraphData.Vertex.TotalVertexCount())
+ drt := options.GetString(dg.WContext.Params, "degree.direction")
+ switch drt {
+ case "out":
+ dg.direction = 0
+ case "in":
+ dg.direction = 1
+ case "both":
+ dg.direction = 2
+ default:
+ logrus.Errorf("please input 'out', 'in' or 'both'.")
+ return fmt.Errorf("please input 'out', 'in' or 'both'")
+ }
+ return nil
+}
+
+func (dg *DegreeWorker) BeforeStep() {
+}
+
+func (dg *DegreeWorker) Compute(vertexId uint32, pId int) {
+ _ = pId
+ var vertexIdx = vertexId - dg.WContext.GraphData.VertIDStart
+ switch dg.direction {
+ case 0:
+ dg.newValues[vertexId] = dg.WContext.GraphData.Edges.GetOutDegree(vertexId)
+ case 1:
+ dg.newValues[vertexId] = serialize.SUint32(len(dg.WContext.GraphData.Edges.GetInEdges(vertexIdx)))
+ case 2:
+ dg.newValues[vertexId] = dg.WContext.GraphData.Edges.GetOutDegree(vertexId) +
+ serialize.SUint32(len(dg.WContext.GraphData.Edges.GetInEdges(vertexIdx)))
+ }
+}
+
+func (dg *DegreeWorker) AfterStep() {
+}
+
+func (dg *DegreeWorker) OutputValueType() string {
+ return common.HgValueTypeInt
+}
+
+type DegreeMaster struct {
+ compute.MasterComputerBase
+}
+
+func (dg *DegreeMaster) Compute() bool {
+ return dg.MContext.Step < 1
+}
diff --git a/vermeer/algorithms/depth.go b/vermeer/algorithms/depth.go
new file mode 100644
index 00000000..7d07f0da
--- /dev/null
+++ b/vermeer/algorithms/depth.go
@@ -0,0 +1,199 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import (
+ "vermeer/apps/common"
+ "vermeer/apps/compute"
+ "vermeer/apps/serialize"
+
+ "github.com/sirupsen/logrus"
+)
+
+func init() {
+ Algorithms = append(Algorithms, new(DepthMaker))
+}
+
+type DepthMaker struct {
+ compute.AlgorithmMakerBase
+}
+
+func (dg *DepthMaker) CreateWorkerComputer() compute.WorkerComputer {
+ return &DepthWorker{}
+}
+
+func (dg *DepthMaker) CreateMasterComputer() compute.MasterComputer {
+ return &DepthMaster{}
+}
+
+func (dg *DepthMaker) Name() string {
+ return "depth"
+}
+
+func (dg *DepthMaker) DataNeeded() []string {
+ return []string{compute.UseOutEdge}
+}
+
+func (dg *DepthMaker) SupportedStatistics() map[compute.StatisticsType]struct{} {
+ return map[compute.StatisticsType]struct{}{compute.StatisticsTypeSketchDepth: {}}
+}
+
+type DepthWorker struct {
+ compute.WorkerComputerBase
+ endWccStep bool
+ isFindEnd bool
+ endPoint uint32
+ depth []serialize.SUint32
+ diffs []int32
+ baseStep int32
+}
+
+func (dg *DepthWorker) VertexValue(i uint32) serialize.MarshalAble {
+ return &dg.depth[i]
+}
+
+func (dg *DepthWorker) Init() error {
+ dg.isFindEnd = false
+ dg.depth = make([]serialize.SUint32, dg.WContext.GraphData.Vertex.TotalVertexCount())
+ dg.diffs = make([]int32, dg.WContext.Parallel)
+
+ dg.endWccStep = false
+ for i := uint32(0); i < dg.WContext.GraphData.Vertex.TotalVertexCount(); i++ {
+ dg.depth[i] = serialize.SUint32(i)
+ }
+
+ dg.WContext.CreateValue("diff_sum", compute.ValueTypeInt32, compute.CValueActionAggregate)
+ dg.WContext.SetValue("diff_sum", serialize.SInt32(0))
+ dg.WContext.CreateValue("find_end", compute.ValueTypeInt32, compute.CValueActionAggregate)
+ dg.WContext.SetValue("find_end", serialize.SInt32(0))
+ dg.WContext.CreateValue("wcc_end", compute.ValueTypeInt32, compute.CValueActionAggregate)
+ dg.WContext.SetValue("wcc_end", serialize.SInt32(0))
+ return nil
+}
+
+func (dg *DepthWorker) BeforeStep() {
+ for i := range dg.diffs {
+ dg.diffs[i] = 0
+ }
+ wccEnd := dg.WContext.GetValue("wcc_end").(serialize.SInt32) > 0
+ if !dg.endWccStep && wccEnd {
+ dg.endWccStep = true
+ dg.baseStep = dg.WContext.Step - 1
+ for i, v := range dg.depth {
+ dg.depth[i] = 0
+ dg.depth[v] = 1
+ }
+ }
+ endFind := dg.WContext.GetValue("find_end").(serialize.SInt32) > 0
+ if !dg.isFindEnd && endFind {
+ dg.isFindEnd = true
+ dg.baseStep = dg.WContext.Step - 1
+ maxEnd := 0
+ for vertexID, depth := range dg.depth {
+ if depth > serialize.SUint32(maxEnd) {
+ maxEnd = int(depth)
+ dg.endPoint = uint32(vertexID)
+ }
+ }
+ logrus.Infof("find end point: %v, vertexID:%v", dg.endPoint, dg.WContext.GraphData.Vertex.GetVertex(dg.endPoint).ID)
+ // clear depth
+ for i := range dg.depth {
+ dg.depth[i] = serialize.SUint32(0)
+ }
+ dg.depth[dg.endPoint] = serialize.SUint32(1)
+ }
+
+}
+
+func (dg *DepthWorker) Compute(vertexID uint32, pID int) {
+ vertIDx := vertexID - dg.WContext.GraphData.VertIDStart
+ if !dg.endWccStep {
+ // do wcc
+ oldValue := dg.depth[vertexID]
+ for _, nID := range dg.WContext.GraphData.Edges.GetInEdges(vertIDx) {
+ if dg.depth[vertexID] > dg.depth[nID] {
+ dg.depth[vertexID] = dg.depth[nID]
+ }
+ }
+ for _, nID := range dg.WContext.GraphData.Edges.GetOutEdges(vertIDx) {
+ if dg.depth[vertexID] > dg.depth[nID] {
+ dg.depth[vertexID] = dg.depth[nID]
+ }
+ }
+ //}
+ if oldValue != dg.depth[vertexID] {
+ dg.diffs[pID]++
+ }
+ } else {
+ // find max depth
+ if dg.depth[vertexID] > 0 {
+ return
+ }
+ for _, nID := range dg.WContext.GraphData.Edges.GetInEdges(vertIDx) {
+ if dg.depth[nID] == serialize.SUint32(dg.WContext.Step-dg.baseStep) {
+ dg.depth[vertexID] = serialize.SUint32(dg.WContext.Step - dg.baseStep + 1)
+ dg.diffs[pID]++
+ return
+ }
+ }
+ for _, nID := range dg.WContext.GraphData.Edges.GetOutEdges(vertIDx) {
+ if dg.depth[nID] == serialize.SUint32(dg.WContext.Step-dg.baseStep) {
+ dg.depth[vertexID] = serialize.SUint32(dg.WContext.Step - dg.baseStep + 1)
+ dg.diffs[pID]++
+ return
+ }
+ }
+ }
+}
+
+func (dg *DepthWorker) AfterStep() {
+ var diffSum int32
+ for _, v := range dg.diffs {
+ diffSum += v
+ }
+ dg.WContext.SetValue("diff_sum", serialize.SInt32(diffSum))
+}
+
+func (dg *DepthWorker) OutputValueType() string {
+ return common.HgValueTypeInt
+}
+
+type DepthMaster struct {
+ compute.MasterComputerBase
+ endWccStep bool
+ isFindEnd bool
+}
+
+func (dg *DepthMaster) Compute() bool {
+ diffSum := dg.MContext.GetValue("diff_sum").(serialize.SInt32)
+ logrus.Infof("different sum: %v", diffSum)
+ if diffSum == 0 {
+ if !dg.endWccStep {
+ logrus.Infof("wcc step end!")
+ dg.MContext.SetValue("wcc_end", serialize.SInt32(1))
+ dg.endWccStep = true
+ } else if !dg.isFindEnd {
+ logrus.Infof("find end point!")
+ dg.MContext.SetValue("find_end", serialize.SInt32(1))
+ dg.isFindEnd = true
+ } else {
+ return false
+ }
+ }
+ return true
+}
diff --git a/vermeer/algorithms/jaccard.go b/vermeer/algorithms/jaccard.go
new file mode 100644
index 00000000..6d195032
--- /dev/null
+++ b/vermeer/algorithms/jaccard.go
@@ -0,0 +1,145 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import (
+ "fmt"
+ "vermeer/apps/common"
+ "vermeer/apps/compute"
+ "vermeer/apps/options"
+ "vermeer/apps/serialize"
+
+ "github.com/sirupsen/logrus"
+)
+
+func init() {
+ Algorithms = append(Algorithms, new(JaccardMaker))
+}
+
+type JaccardMaker struct {
+ compute.AlgorithmMakerBase
+}
+
+func (jm *JaccardMaker) CreateWorkerComputer() compute.WorkerComputer {
+ return &JaccardWorker{}
+}
+
+func (jm *JaccardMaker) CreateMasterComputer() compute.MasterComputer {
+ return &JaccardMaster{}
+}
+
+func (jm *JaccardMaker) Name() string {
+ return "jaccard"
+}
+
+func (jm *JaccardMaker) DataNeeded() []string {
+ return []string{compute.UseOutEdge}
+}
+
+type JaccardWorker struct {
+ compute.WorkerComputerBase
+ sourceID uint32
+ sourceNeighbors map[serialize.SUint32]struct{}
+ jaccardSimilarity []serialize.SFloat32
+}
+
+func (jw *JaccardWorker) VertexValue(i uint32) serialize.MarshalAble {
+ return &jw.jaccardSimilarity[i]
+}
+
+func (jw *JaccardWorker) Init() error {
+ var ok bool
+ jw.sourceID, ok = jw.WContext.GraphData.Vertex.GetVertexIndex(options.GetString(jw.WContext.Params, "jaccard.source"))
+ if !ok {
+ logrus.Errorf("jaccard.source not exist:%v", options.GetString(jw.WContext.Params, "jaccard.source"))
+ return fmt.Errorf("jaccard.source not exist:%v", options.GetString(jw.WContext.Params, "jaccard.source"))
+ }
+ jw.WContext.CreateValue("source_neighbors", compute.ValueTypeSliceUint32, compute.CValueActionAggregate)
+ jw.WContext.SetValue("source_neighbors", serialize.SliceUint32{})
+ jw.jaccardSimilarity = make([]serialize.SFloat32, jw.WContext.GraphData.Vertex.TotalVertexCount())
+ return nil
+}
+
+func (jw *JaccardWorker) BeforeStep() {
+ if jw.WContext.Step > 1 {
+ sourceNeighborSlice := jw.WContext.GetValue("source_neighbors").(serialize.SliceUint32)
+ jw.sourceNeighbors = make(map[serialize.SUint32]struct{}, len(sourceNeighborSlice))
+ for _, neighbor := range sourceNeighborSlice {
+ jw.sourceNeighbors[neighbor] = struct{}{}
+ }
+ }
+}
+
+func (jw *JaccardWorker) Compute(vertexId uint32, pId int) {
+ _ = pId
+ vertID := vertexId - jw.WContext.GraphData.VertIDStart
+ inEdges := jw.WContext.GraphData.Edges.GetInEdges(vertID)
+ outEdges := jw.WContext.GraphData.Edges.GetOutEdges(vertID)
+ if jw.WContext.Step == 1 {
+ if vertexId == jw.sourceID {
+ neighbors := make(map[serialize.SUint32]struct{}, len(inEdges)+len(outEdges))
+ for _, edge := range inEdges {
+ neighbors[edge] = struct{}{}
+ }
+ for _, edge := range outEdges {
+ neighbors[edge] = struct{}{}
+ }
+ sourceNeighbors := make([]serialize.SUint32, 0, len(neighbors))
+ for edge := range neighbors {
+ sourceNeighbors = append(sourceNeighbors, edge)
+ }
+ jw.WContext.SetValue("source_neighbors", serialize.SliceUint32(sourceNeighbors))
+ }
+ } else {
+ selfNeighbors := make(map[serialize.SUint32]struct{}, len(inEdges)+len(outEdges))
+ for _, edge := range inEdges {
+ selfNeighbors[edge] = struct{}{}
+ }
+ for _, edge := range outEdges {
+ selfNeighbors[edge] = struct{}{}
+ }
+ count := 0
+ if len(jw.sourceNeighbors) > len(selfNeighbors) {
+ for selfNeighbor := range selfNeighbors {
+ if _, ok := jw.sourceNeighbors[selfNeighbor]; ok {
+ count++
+ }
+ }
+ } else {
+ for sourceNeighbor := range jw.sourceNeighbors {
+ if _, ok := selfNeighbors[sourceNeighbor]; ok {
+ count++
+ }
+ }
+ }
+ jw.jaccardSimilarity[vertexId] =
+ serialize.SFloat32(count) / serialize.SFloat32(len(selfNeighbors)+len(jw.sourceNeighbors)-count)
+ }
+}
+
+func (jw *JaccardWorker) OutputValueType() string {
+ return common.HgValueTypeInt
+}
+
+type JaccardMaster struct {
+ compute.MasterComputerBase
+}
+
+func (dg *JaccardMaster) Compute() bool {
+ return dg.MContext.Step < 2
+}
diff --git a/vermeer/algorithms/kcore.go b/vermeer/algorithms/kcore.go
new file mode 100644
index 00000000..22f3b772
--- /dev/null
+++ b/vermeer/algorithms/kcore.go
@@ -0,0 +1,144 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import (
+ "vermeer/apps/common"
+ "vermeer/apps/compute"
+ "vermeer/apps/options"
+ "vermeer/apps/serialize"
+)
+
+func init() {
+ Algorithms = append(Algorithms, new(KcoreMaker))
+}
+
+type KcoreMaker struct {
+ compute.AlgorithmMakerBase
+}
+
+func (kc *KcoreMaker) CreateWorkerComputer() compute.WorkerComputer {
+ return &KcoreWorker{}
+}
+
+func (kc *KcoreMaker) CreateMasterComputer() compute.MasterComputer {
+ return &KcoreMaster{}
+}
+
+func (kc *KcoreMaker) Name() string {
+ return "kcore"
+}
+
+func (kc *KcoreMaker) DataNeeded() []string {
+ return []string{compute.UseOutEdge}
+}
+
+type KcoreWorker struct {
+ compute.WorkerComputerBase
+ oldDegrees []serialize.SUint32
+ newDegrees []serialize.SUint32
+ node2neigh [][]serialize.SUint32
+ stopFlag serialize.SInt32
+ k serialize.SUint32
+}
+
+func (kw *KcoreWorker) VertexValue(i uint32) serialize.MarshalAble {
+ return &kw.newDegrees[i]
+}
+
+func (kw *KcoreWorker) Init() error {
+ kw.k = serialize.SUint32(options.GetInt(kw.WContext.Params, "kcore.degree_k"))
+ kw.oldDegrees = make([]serialize.SUint32, kw.WContext.GraphData.Vertex.TotalVertexCount())
+ kw.newDegrees = make([]serialize.SUint32, kw.WContext.GraphData.Vertex.TotalVertexCount())
+ kw.node2neigh = make([][]serialize.SUint32, kw.WContext.GraphData.Vertex.TotalVertexCount())
+ kw.WContext.CreateValue("is_stop", compute.ValueTypeInt32, compute.CValueActionAggregate)
+ return nil
+}
+
+func (kw *KcoreWorker) BeforeStep() {
+ kw.stopFlag = 1
+}
+
+func (kw *KcoreWorker) Compute(vertexId uint32, pId int) {
+ _ = pId
+ vertexIdx := vertexId - kw.WContext.GraphData.VertIDStart
+ if kw.WContext.Step == 1 {
+ kw.stopFlag = 0
+ inEdges := kw.WContext.GraphData.Edges.GetInEdges(vertexIdx)
+ outEdges := kw.WContext.GraphData.Edges.GetOutEdges(vertexIdx)
+ //neighNode存储邻接节点集合, key:neighNode
+ neighNodes := make(map[uint32]int8, len(inEdges)+len(outEdges))
+ for _, inNode := range inEdges {
+ neighNodes[uint32(inNode)] = 1
+ }
+ for _, outNode := range outEdges {
+ neighNodes[uint32(outNode)] = 1
+ }
+ //}
+ //初始化每个节点newDegree的值为邻接节点的个数
+ kw.newDegrees[vertexId] = serialize.SUint32(len(neighNodes))
+ //node2neigh保存每个节点的邻接节点
+ kw.node2neigh[vertexId] = make([]serialize.SUint32, len(neighNodes))
+ i := 0
+ for neighNode := range neighNodes {
+ kw.node2neigh[vertexId][i] = serialize.SUint32(neighNode)
+ i++
+ }
+ return
+ }
+ //节点度数为0,说明已被剔除,不再计算该节点
+ if kw.oldDegrees[vertexId] == 0 {
+ return
+ }
+ //节点度数小于k,度数置0剔除且step继续
+ if kw.oldDegrees[vertexId] < kw.k {
+ kw.stopFlag = 0
+ kw.newDegrees[vertexId] = 0
+ return
+ }
+ //节点度数大于等于k, 若邻接节点中有度数小于k的, 自身newDegree减一
+ for _, neighNode := range kw.node2neigh[vertexId] {
+ if kw.oldDegrees[neighNode] > 0 && kw.oldDegrees[neighNode] < kw.k {
+ kw.stopFlag = 0
+ kw.newDegrees[vertexId]--
+ }
+ }
+}
+
+func (kw *KcoreWorker) AfterStep() {
+ kw.WContext.SetValue("is_stop", kw.stopFlag)
+ //更新oldDegree
+ for i := range kw.oldDegrees {
+ kw.oldDegrees[i] = kw.newDegrees[i]
+ }
+}
+
+func (kw *KcoreWorker) OutputValueType() string {
+ return common.HgValueTypeInt
+}
+
+type KcoreMaster struct {
+ compute.MasterComputerBase
+}
+
+func (km *KcoreMaster) Compute() bool {
+ //stopFlag =0 表示继续迭代;=1 表示终止
+ stopFlag := km.MContext.GetValue("is_stop").(serialize.SInt32)
+ //所有worker的stopFlag全为1时,task终止
+ return stopFlag != serialize.SInt32(len(km.MContext.WorkerCValues))
+}
diff --git a/vermeer/algorithms/kout.go b/vermeer/algorithms/kout.go
new file mode 100644
index 00000000..fc7d8148
--- /dev/null
+++ b/vermeer/algorithms/kout.go
@@ -0,0 +1,199 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import (
+ "fmt"
+ "vermeer/apps/common"
+ "vermeer/apps/compute"
+ "vermeer/apps/options"
+ "vermeer/apps/serialize"
+
+ "github.com/sirupsen/logrus"
+)
+
+func init() {
+ Algorithms = append(Algorithms, new(KoutMaker))
+}
+
+type KoutMaker struct {
+ compute.AlgorithmMakerBase
+}
+
+func (km *KoutMaker) CreateWorkerComputer() compute.WorkerComputer {
+ return &KoutWorker{}
+}
+
+func (km *KoutMaker) CreateMasterComputer() compute.MasterComputer {
+ return &KoutMaster{}
+}
+
+func (km *KoutMaker) Name() string {
+ return "kout"
+}
+
+func (km *KoutMaker) DataNeeded() []string {
+ return []string{compute.UseOutEdge}
+}
+
+type directionType int
+
+const (
+ directionBoth directionType = iota
+ directionIn
+ directionOut
+)
+
+type KoutWorker struct {
+ compute.WorkerComputerBase
+ vertValues []serialize.SUint8
+ direction directionType
+ diff []int32
+}
+
+// Init 初始化函数
+func (kw *KoutWorker) Init() error {
+ kw.vertValues = make([]serialize.SUint8, kw.WContext.GraphData.Vertex.TotalVertexCount())
+ source := options.GetString(kw.WContext.Params, "kout.source")
+ srcVertID, ok := kw.WContext.GraphData.Vertex.GetVertexIndex(source)
+ if !ok {
+ logrus.Errorf("source not exists: %s", source)
+ return fmt.Errorf("source not exists: %s", source)
+ }
+ kw.vertValues[srcVertID] = 1
+ direction := options.GetString(kw.WContext.Params, "kout.direction")
+ // direction can be "in", "out", "both"
+ switch direction {
+ case "both":
+ kw.direction = directionBoth
+ case "in":
+ // direction is "in" means use out-edge
+ kw.direction = directionOut
+ case "out":
+ // direction is "out" means use in-edge
+ kw.direction = directionIn
+ default:
+ logrus.Errorf("direction not exists: %s", direction)
+ return fmt.Errorf("direction not exists: %s", direction)
+ }
+ kw.diff = make([]int32, kw.WContext.Parallel)
+ kw.WContext.CreateValue("diff_sum", compute.ValueTypeInt32, compute.CValueActionAggregate)
+ kw.WContext.SetValue("diff_sum", serialize.SInt32(0))
+ logrus.Infof("kout init source: %s, shortId: %d, direction: %v", source, srcVertID, direction)
+ return nil
+}
+
+// VertexValue 返回与给定索引i对应的顶点值的序列化接口
+// 参数:
+// i:给定的索引
+// 返回值:
+// 返回一个指向kw.vertValues[i]的指针,该元素实现了serialize.MarshalAble接口
+func (kw *KoutWorker) VertexValue(i uint32) serialize.MarshalAble {
+ return &kw.vertValues[i]
+}
+
+// BeforeStep 方法在执行任务前调用,将kw.diff数组的每个元素清零
+func (kw *KoutWorker) BeforeStep() {
+ for i := 0; i < len(kw.diff); i++ {
+ kw.diff[i] = 0
+ }
+}
+
+// Compute 函数是KoutWorker类型的方法,用于执行计算操作
+//
+// 参数:
+// - vertexID:待计算的顶点ID
+// - pID:用于标识当前计算的进程ID
+//
+// 返回值:
+// - 无返回值
+func (kw *KoutWorker) Compute(vertexID uint32, pID int) {
+ if kw.vertValues[vertexID] > 0 {
+ return
+ }
+ vertIDx := vertexID - kw.WContext.GraphData.VertIDStart
+ switch kw.direction {
+ case directionBoth:
+ if kw.computeInEdges(vertexID, vertIDx) {
+ kw.diff[pID]++
+ return
+ }
+ if kw.computeOutEdges(vertexID, vertIDx) {
+ kw.diff[pID]++
+ return
+ }
+ case directionIn:
+ if kw.computeInEdges(vertexID, vertIDx) {
+ kw.diff[pID]++
+ return
+ }
+ case directionOut:
+ if kw.computeOutEdges(vertexID, vertIDx) {
+ kw.diff[pID]++
+ return
+ }
+ }
+}
+
+// computeInEdges 函数接收两个参数,分别表示顶点ID和短ID,返回一个bool值
+// 该函数会遍历当前顶点的所有入边,检查每个入边的顶点的值是否与当前步骤相同
+// 如果相同,则将当前顶点的值更新为当前步骤+1,并返回true;否则返回false
+func (kw *KoutWorker) computeInEdges(vertexID uint32, vShortID uint32) (changed bool) {
+ for _, nID := range kw.WContext.GraphData.Edges.GetInEdges(vShortID) {
+ if kw.vertValues[nID] == serialize.SUint8(kw.WContext.Step) {
+ kw.vertValues[vertexID] = serialize.SUint8(kw.WContext.Step + 1)
+ return true
+ }
+ }
+ return false
+}
+
+// computeOutEdges 计算给定顶点ID和短ID的出边,如果存在一条出边对应的顶点值与当前步骤相同,则将当前顶点的值更新为当前步骤+1,返回true,否则返回false
+func (kw *KoutWorker) computeOutEdges(vertexID uint32, vShortID uint32) (changed bool) {
+ for _, nID := range kw.WContext.GraphData.Edges.GetOutEdges(vShortID) {
+ if kw.vertValues[nID] == serialize.SUint8(kw.WContext.Step) {
+ kw.vertValues[vertexID] = serialize.SUint8(kw.WContext.Step + 1)
+ return true
+ }
+ }
+ return false
+}
+
+// AfterStep 方法计算所有 diff 的和,并将结果保存在 WContext 中
+func (kw *KoutWorker) AfterStep() {
+ var diffSum int32
+ for _, v := range kw.diff {
+ diffSum += v
+ }
+ kw.WContext.SetValue("diff_sum", serialize.SInt32(diffSum))
+}
+
+func (kw *KoutWorker) OutputValueType() string {
+ return common.HgValueTypeInt
+}
+
+type KoutMaster struct {
+ compute.MasterComputerBase
+}
+
+// Compute 方法用于计算KoutMaster实例中的不同值的总和,并返回一个布尔值,表示是否存在不同值
+func (km *KoutMaster) Compute() bool {
+ diffSum := km.MContext.GetValue("diff_sum").(serialize.SInt32)
+ logrus.Infof("different sum: %v", diffSum)
+ return diffSum != 0
+}
diff --git a/vermeer/algorithms/louvain.go b/vermeer/algorithms/louvain.go
new file mode 100644
index 00000000..1e9b58c7
--- /dev/null
+++ b/vermeer/algorithms/louvain.go
@@ -0,0 +1,710 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import (
+ "math"
+ "sync"
+ "sync/atomic"
+ "vermeer/apps/common"
+ "vermeer/apps/compute"
+ "vermeer/apps/options"
+ "vermeer/apps/serialize"
+
+ "github.com/sirupsen/logrus"
+)
+
+func init() {
+ Algorithms = append(Algorithms, new(LouvainMaker))
+}
+
+type LouvainMaker struct {
+ compute.AlgorithmMakerBase
+}
+
+func (lv *LouvainMaker) CreateWorkerComputer() compute.WorkerComputer {
+ return &LouvainWorker{}
+}
+
+func (lv *LouvainMaker) CreateMasterComputer() compute.MasterComputer {
+ return &LouvainMaster{}
+}
+
+func (lv *LouvainMaker) Name() string {
+ return "louvain"
+}
+
+func (lv *LouvainMaker) DataNeeded() []string {
+ return []string{compute.UseOutEdge}
+}
+
+func (lv *LouvainMaker) SupportedStatistics() map[compute.StatisticsType]struct{} {
+ return map[compute.StatisticsType]struct{}{compute.StatisticsTypeCount: {}, compute.StatisticsTypeModularity: {}, compute.StatisticsTypeSketchCount: {}}
+}
+
+// louvainNode 图中合并的每个节点
+type louvainNode struct {
+ //node 本节点内的所有顶点
+ vertex []serialize.SUint32
+ //本节点的所有邻居节点
+ neighbors map[serialize.SUint32]float64
+ //本节点的所有度之和
+ kI float64
+ //本节点内部的连接度权重之和
+ KIn float64
+ //本节点当前所在的社区id
+ commID serialize.SUint32
+ //once
+ once int32
+}
+
+type community struct {
+ //社区包含的点
+ node map[serialize.SUint32]struct{}
+ //社区的总度数
+ sigmaTot float64
+}
+
+type LouvainWorker struct {
+ compute.WorkerComputerBase
+ //nodeID
+ nodeID []serialize.SUint32
+ //node struct map
+ nodes map[serialize.SUint32]*louvainNode
+ //louvain step聚合后社区相关信息
+ communities map[serialize.SUint32]*community
+ //所有点的邻居节点
+ neighborEdges []serialize.SliceUint32
+ //最终的节点变化
+ node2comm []map[serialize.SUint32]serialize.SUint32
+ //误分节点导入至空社区
+ emptyComm serialize.SUint32
+ //neighbor KIin
+ //neighborCommKVInInPID []map[serialize.SUint32]float64
+ //total edge nums,带权重
+ edgeNums float64
+ //resolution : double, optional
+ //Will change the size of the communities, default to 1.
+ //represents the time described in
+ //"Laplacian Dynamics and Multiscale Modular Structure in Networks",
+ //R. Lambiotte, J.-C. Delvenne, M. Barahona
+ resolution float64
+ //parallel
+ parallel int
+ //第一阶段,以顶点为单位
+ firstStep bool
+ firstStepKI []float64
+ firstStepCommID []serialize.SUint32
+}
+
+func (lw *LouvainWorker) VertexValue(i uint32) serialize.MarshalAble {
+ if lw.WContext.Step == 1 {
+ return &lw.neighborEdges[i]
+ }
+ if lw.WContext.Output {
+ return &lw.nodeID[i]
+ }
+ nilValue := serialize.SInt32(0)
+ return &nilValue
+}
+
+func (lw *LouvainWorker) Init() error {
+ lw.communities = make(map[serialize.SUint32]*community, lw.WContext.GraphData.Vertex.TotalVertexCount())
+
+ lw.neighborEdges = make([]serialize.SliceUint32, lw.WContext.GraphData.Vertex.TotalVertexCount())
+
+ lw.resolution = options.GetFloat(lw.WContext.Params, "louvain.resolution")
+ lw.parallel = lw.WContext.Parallel
+ if lw.parallel <= 0 {
+ logrus.Infof("parallel value must be larger than 0, get: %v, set to defalut value :1", lw.parallel)
+ lw.parallel = 1
+ }
+ lw.node2comm = make([]map[serialize.SUint32]serialize.SUint32, lw.parallel)
+ for i := range lw.node2comm {
+ lw.node2comm[i] = make(map[serialize.SUint32]serialize.SUint32, lw.WContext.GraphData.Vertex.TotalVertexCount()/uint32(lw.parallel))
+ }
+ //lw.neighborCommKVInInPID = make([]map[serialize.SUint32]float64, lw.parallel)
+ lw.WContext.CreateValue("change_node", compute.ValueTypeSliceUint32, compute.CValueActionAggregate)
+ lw.WContext.SetValue("change_node", serialize.SliceUint32{})
+ lw.WContext.CreateValue("change_comm", compute.ValueTypeSliceUint32, compute.CValueActionAggregate)
+ lw.WContext.SetValue("change_comm", serialize.SliceUint32{})
+ lw.WContext.CreateValue("mod_value", compute.ValueTypeFloat32, compute.CValueActionAggregate)
+ lw.WContext.SetValue("mod_value", serialize.SFloat32(0))
+
+ lw.WContext.CreateValue("update", compute.ValueTypeInt32, compute.CValueActionAggregate)
+ lw.WContext.SetValue("update", serialize.SInt32(0))
+
+ lw.emptyComm = serialize.SUint32(lw.WContext.GraphData.Vertex.TotalVertexCount() + 1)
+ lw.firstStep = true
+ return nil
+}
+
+func (lw *LouvainWorker) BeforeStep() {
+ if lw.WContext.Step == 2 {
+ lw.firstStepKI = make([]float64, lw.WContext.GraphData.Vertex.TotalVertexCount())
+ lw.firstStepCommID = make([]serialize.SUint32, lw.WContext.GraphData.Vertex.TotalVertexCount())
+ for vertexID, edges := range lw.neighborEdges {
+ if len(edges) == 0 {
+ continue
+ }
+ lw.edgeNums += float64(len(edges))
+ lw.communities[serialize.SUint32(vertexID)] = &community{node: map[serialize.SUint32]struct{}{serialize.SUint32(vertexID): {}}}
+ lw.communities[serialize.SUint32(vertexID)].sigmaTot = float64(len(edges))
+ lw.firstStepKI[vertexID] = float64(len(edges))
+ lw.firstStepCommID[vertexID] = serialize.SUint32(vertexID)
+ }
+ lw.resolution /= lw.edgeNums
+ lw.WContext.SetValue("mod_value", serialize.SFloat32(-1))
+ } else if lw.WContext.Step > 2 {
+ changeNode := lw.WContext.GetValue("change_node").(serialize.SliceUint32)
+ changeComm := lw.WContext.GetValue("change_comm").(serialize.SliceUint32)
+ //changes := make(map[serialize.SUint32]serialize.SUint32)
+ currCommIDs := make(map[serialize.SUint32]struct{})
+ moveToEmpty := make([]serialize.SUint32, 0)
+ for i, node := range changeNode {
+ if changeComm[i] == lw.emptyComm {
+ moveToEmpty = append(moveToEmpty, node)
+ continue
+ }
+ var currCommID serialize.SUint32
+ var ki float64
+ if lw.firstStep {
+ currCommID = lw.firstStepCommID[node]
+ ki = lw.firstStepKI[node]
+ lw.firstStepCommID[node] = changeComm[i]
+ } else {
+ currCommID = lw.nodes[node].commID
+ ki = lw.nodes[node].kI
+ lw.nodes[node].commID = changeComm[i]
+ }
+ currCommIDs[currCommID] = struct{}{}
+ delete(lw.communities[currCommID].node, node)
+ lw.communities[currCommID].sigmaTot -= ki
+ lw.communities[changeComm[i]].node[node] = struct{}{}
+ lw.communities[changeComm[i]].sigmaTot += ki
+ }
+ if len(moveToEmpty) > 0 {
+ logrus.Infof("move to empty node len:%v", len(moveToEmpty))
+ emptyComms := make([]serialize.SUint32, len(moveToEmpty))
+ idx := 0
+ for i := 0; i < int(lw.WContext.GraphData.Vertex.TotalVertexCount()); i++ {
+ if idx == len(moveToEmpty) {
+ break
+ }
+ if comm, ok := lw.communities[serialize.SUint32(i)]; ok {
+ if len(comm.node) == 0 {
+ emptyComms[idx] = serialize.SUint32(i)
+ idx++
+ }
+ }
+ }
+ MoveOutComm := make(map[serialize.SUint32]int)
+ for _, node := range moveToEmpty {
+ var currCommID serialize.SUint32
+ if lw.firstStep {
+ currCommID = lw.firstStepCommID[node]
+ } else {
+ currCommID = lw.nodes[node].commID
+ }
+ currCommIDs[currCommID] = struct{}{}
+ MoveOutComm[currCommID] += 1
+ }
+ alreadyMoveOutComm := make(map[serialize.SUint32]int, len(MoveOutComm))
+ for i, node := range moveToEmpty {
+ var ki float64
+ var currCommID serialize.SUint32
+ if lw.firstStep {
+ currCommID = lw.firstStepCommID[node]
+ } else {
+ currCommID = lw.nodes[node].commID
+ }
+ alreadyMoveOutComm[currCommID] += 1
+ if alreadyMoveOutComm[currCommID] > MoveOutComm[currCommID]/2 && MoveOutComm[currCommID] > 1 {
+ continue
+ }
+ if lw.firstStep {
+ ki = lw.firstStepKI[node]
+ lw.firstStepCommID[node] = emptyComms[i]
+ } else {
+ ki = lw.nodes[node].kI
+ lw.nodes[node].commID = emptyComms[i]
+ }
+ delete(lw.communities[currCommID].node, node)
+ lw.communities[currCommID].sigmaTot -= ki
+ lw.communities[emptyComms[i]].node[node] = struct{}{}
+ lw.communities[emptyComms[i]].sigmaTot += ki
+ }
+ }
+ update := lw.WContext.GetValue("update").(serialize.SInt32)
+ if update > 0 {
+ lw.deleteEmptyComm()
+ if lw.firstStep {
+ lw.firstStep = false
+ //初始化node
+ lw.initLouvainNode()
+ //free memory
+ lw.firstStepCommID = nil
+ lw.firstStepKI = nil
+ lw.neighborEdges = nil
+ } else {
+ //生成新图
+ lw.genNewGraph()
+ }
+ lw.WContext.SetValue("mod_value", serialize.SFloat32(lw.calModularity()))
+ } else {
+ lw.optimizeMem(currCommIDs)
+ lw.WContext.SetValue("mod_value", serialize.SFloat32(-1))
+ }
+ lw.node2comm = make([]map[serialize.SUint32]serialize.SUint32, lw.parallel)
+ for i := range lw.node2comm {
+ lw.node2comm[i] = make(map[serialize.SUint32]serialize.SUint32, len(changeNode)/lw.parallel)
+ }
+ for nodeID := range lw.nodes {
+ lw.nodes[nodeID].once = 0
+ }
+ }
+ logrus.Infof("communities num:%v", len(lw.communities))
+}
+
+func (lw *LouvainWorker) Compute(vertexID uint32, pID int) {
+ //step 1:同步所有顶点的邻边
+ vertID := vertexID - lw.WContext.GraphData.VertIDStart
+ if len(lw.WContext.GraphData.Edges.GetInEdges(vertID))+len(lw.WContext.GraphData.Edges.GetOutEdges(vertID)) == 0 {
+ return
+ }
+ if lw.WContext.Step == 1 {
+ lw.neighborEdges[vertexID] = make([]serialize.SUint32, 0, len(lw.WContext.GraphData.Edges.GetInEdges(vertID)))
+ tempMap := make(map[serialize.SUint32]struct{})
+ tempMap[serialize.SUint32(vertexID)] = struct{}{}
+ for _, edge := range lw.WContext.GraphData.Edges.GetInEdges(vertID) {
+ if _, ok := tempMap[edge]; !ok {
+ lw.neighborEdges[vertexID] = append(lw.neighborEdges[vertexID], edge)
+ tempMap[edge] = struct{}{}
+ }
+ }
+ for _, edge := range lw.WContext.GraphData.Edges.GetOutEdges(vertID) {
+ if _, ok := tempMap[edge]; !ok {
+ lw.neighborEdges[vertexID] = append(lw.neighborEdges[vertexID], edge)
+ tempMap[edge] = struct{}{}
+ }
+ }
+ } else {
+ if lw.firstStep {
+ //以vertex为基本单元计算
+ currCommID := lw.firstStepCommID[vertexID]
+ kI := lw.firstStepKI[vertexID]
+
+ //neighborCommKIin 计算neighbor社区的KIin
+ neighborCommKVInInPID := make(map[serialize.SUint32]float64, len(lw.neighborEdges[vertexID]))
+
+ for _, neighbor := range lw.neighborEdges[vertexID] {
+ neighborCommID := lw.firstStepCommID[neighbor]
+ neighborCommKVInInPID[neighborCommID] += 1
+ }
+
+ var maxDeltaQ float64
+ targetCommID := currCommID
+ for neighborCommID, kVIn := range neighborCommKVInInPID {
+ sigmaTot := lw.communities[neighborCommID].sigmaTot
+ if currCommID == neighborCommID {
+ sigmaTot -= kI
+ }
+ commDeltaQ := lw.calDeltaQ(kVIn, sigmaTot, kI)
+ if commDeltaQ > maxDeltaQ {
+ targetCommID = neighborCommID
+ maxDeltaQ = commDeltaQ
+ }
+ }
+ if maxDeltaQ == 0 && len(lw.communities[currCommID].node) > 1 {
+ lw.node2comm[pID][serialize.SUint32(vertexID)] = lw.emptyComm
+ }
+ if targetCommID >= currCommID {
+ return
+ }
+ lw.node2comm[pID][serialize.SUint32(vertexID)] = targetCommID
+ } else {
+ nodeID := lw.nodeID[vertexID]
+ if lw.nodes[nodeID] == nil || atomic.LoadInt32(&lw.nodes[nodeID].once) > 0 {
+ return
+ }
+ atomic.AddInt32(&lw.nodes[nodeID].once, 1)
+ currCommID := lw.nodes[nodeID].commID
+ kI := lw.nodes[nodeID].kI
+
+ //neighborCommKIin 计算neighbor社区的KIin
+ neighborCommKVInInPID := make(map[serialize.SUint32]float64, len(lw.nodes[nodeID].neighbors))
+ for neighbor, weight := range lw.nodes[nodeID].neighbors {
+ neighborCommID := lw.nodes[neighbor].commID
+ neighborCommKVInInPID[neighborCommID] += weight
+ }
+
+ var maxDeltaQ float64
+ targetCommID := currCommID
+ for neighborCommID, kVIn := range neighborCommKVInInPID {
+ sigmaTot := lw.communities[neighborCommID].sigmaTot
+ if currCommID == neighborCommID {
+ sigmaTot -= kI
+ }
+ commDeltaQ := lw.calDeltaQ(kVIn, sigmaTot, kI)
+ if commDeltaQ > maxDeltaQ {
+ targetCommID = neighborCommID
+ maxDeltaQ = commDeltaQ
+ }
+ }
+
+ if maxDeltaQ == 0 && len(lw.communities[currCommID].node) > 1 {
+ lw.node2comm[pID][nodeID] = lw.emptyComm
+ }
+ if targetCommID >= currCommID {
+ return
+ }
+ lw.node2comm[pID][nodeID] = targetCommID
+ }
+ }
+}
+
+func (lw *LouvainWorker) AfterStep() {
+ if lw.WContext.Step >= 2 {
+ changeNode := make([]serialize.SUint32, 0, len(lw.node2comm))
+ changeComm := make([]serialize.SUint32, 0, len(lw.node2comm))
+ for _, node2comm := range lw.node2comm {
+ for node, comm := range node2comm {
+ changeNode = append(changeNode, node)
+ changeComm = append(changeComm, comm)
+ }
+ }
+ //logrus.Infof("changenode:%v,changecomm:%v", changeNode, changeComm)
+ lw.WContext.SetValue("change_node", serialize.SliceUint32(changeNode))
+ lw.WContext.SetValue("change_comm", serialize.SliceUint32(changeComm))
+ }
+}
+
+func (lw *LouvainWorker) OutputValueType() string {
+ return common.HgValueTypeInt
+}
+
+func (lw *LouvainWorker) optimizeMem(currCommIDs map[serialize.SUint32]struct{}) {
+ //优化内存
+ commIDs := make([]serialize.SUint32, 0, len(currCommIDs))
+ for commID := range currCommIDs {
+ commIDs = append(commIDs, commID)
+ }
+ partCnt := len(commIDs)/lw.parallel + 1
+ wg := &sync.WaitGroup{}
+ for i := 0; i < lw.parallel; i++ {
+ wg.Add(1)
+ go func(pID int) {
+ defer wg.Done()
+ bIdx := partCnt * pID
+ if bIdx > len(commIDs) {
+ return
+ }
+ eIdx := bIdx + partCnt
+ if eIdx > len(commIDs) {
+ eIdx = len(commIDs)
+ }
+ for i := bIdx; i < eIdx; i++ {
+ commID := commIDs[i]
+ newNodes := make(map[serialize.SUint32]struct{}, len(lw.communities[commID].node))
+ for node := range lw.communities[commID].node {
+ newNodes[node] = struct{}{}
+ }
+ lw.communities[commID].node = newNodes
+ }
+ }(i)
+ }
+ wg.Wait()
+ //for commID := range currCommIDs {
+ // newNodes := make(map[serialize.SUint32]struct{}, len(lw.communities[commID].node))
+ // for node := range lw.communities[commID].node {
+ // newNodes[node] = struct{}{}
+ // }
+ // lw.communities[commID].node = newNodes
+ //}
+}
+
+func (lw *LouvainWorker) deleteEmptyComm() {
+ //删除空的社区
+ for commID, comm := range lw.communities {
+ if len(comm.node) == 0 {
+ delete(lw.communities, commID)
+ }
+ }
+ newComm := make(map[serialize.SUint32]*community, len(lw.communities))
+ for commID, comm := range lw.communities {
+ newComm[commID] = comm
+ }
+ lw.communities = newComm
+}
+
+func (lw *LouvainWorker) calDeltaQ(kVIn, sigmaTot, kI float64) float64 {
+ //DeltaQ = k_v_in - tot * k_v / m
+ //各元素物理意义:
+ //k_v_in: 当前点指向目标点所在社区的边权值之和
+ //tot: 目标点所在社区内边外边权重之和(如果当前点和目标点处在同一个社区,去要减掉一个k_v)
+ //k_v: 当前点内外度之和
+ //m: 全图边权重之和,已处理在resolution之内
+ return kVIn - lw.resolution*sigmaTot*kI
+}
+
+func (lw *LouvainWorker) calModularity() float64 {
+ //模块度计算,可以实现并行计算
+ //.. math::
+ //Q = \sum_{c=1}^{n}
+ //\left[ \frac{L_c}{m} - \gamma\left( \frac{k_c}{2m} \right) ^2 \right]
+ //
+ //where the sum iterates over all communities $c$, $m$ is the number of edges,
+ //$L_c$ is the number of intra-community links for community $c$,
+ //$k_c$ is the sum of degrees of the nodes in community $c$,
+ //and $\gamma$ is the resolution parameter.
+ var mod float64
+ commIDs := make([]serialize.SUint32, 0, len(lw.communities))
+ for commID := range lw.communities {
+ commIDs = append(commIDs, commID)
+ }
+ wg := &sync.WaitGroup{}
+ locker := &sync.Mutex{}
+ partCnt := len(commIDs)/lw.parallel + 1
+ for i := 0; i < lw.parallel; i++ {
+ wg.Add(1)
+ go func(pID int) {
+ defer wg.Done()
+ bIdx := partCnt * pID
+ if bIdx > len(commIDs) {
+ return
+ }
+ eIdx := bIdx + partCnt
+ if eIdx > len(commIDs) {
+ eIdx = len(commIDs)
+ }
+ var modInPID float64
+ for i := bIdx; i < eIdx; i++ {
+ commID := commIDs[i]
+ if int(commID)%lw.WContext.Workers == lw.WContext.WorkerIdx {
+ comm := lw.communities[commID]
+ var commInDegree float64
+ for nodeID := range comm.node {
+ commInDegree += lw.nodes[nodeID].KIn
+ for neighborID, weight := range lw.nodes[nodeID].neighbors {
+ if lw.nodes[neighborID].commID == commID {
+ commInDegree += weight
+ }
+ }
+ }
+ modInPID += commInDegree/lw.edgeNums - (comm.sigmaTot/lw.edgeNums)*comm.sigmaTot*lw.resolution
+ }
+ }
+ locker.Lock()
+ mod += modInPID
+ locker.Unlock()
+ }(i)
+ }
+ wg.Wait()
+ return mod
+}
+
+func (lw *LouvainWorker) initLouvainNode() {
+ //gen a new graph for new step
+ //可以并行
+ lw.nodes = make(map[serialize.SUint32]*louvainNode, len(lw.communities))
+ lw.nodeID = make([]serialize.SUint32, lw.WContext.GraphData.Vertex.TotalVertexCount())
+ for i := range lw.nodeID {
+ lw.nodeID[i] = serialize.SUint32(i)
+ }
+ locker := &sync.Mutex{}
+ commIDs := make([]serialize.SUint32, 0, len(lw.communities))
+ for commID := range lw.communities {
+ commIDs = append(commIDs, commID)
+ }
+ wg := &sync.WaitGroup{}
+ partCnt := len(commIDs)/lw.parallel + 1
+ for i := 0; i < lw.parallel; i++ {
+ wg.Add(1)
+ go func(pID int) {
+ defer wg.Done()
+ bIdx := partCnt * pID
+ if bIdx > len(commIDs) {
+ return
+ }
+ eIdx := bIdx + partCnt
+ if eIdx > len(commIDs) {
+ eIdx = len(commIDs)
+ }
+ newNodesInPID := make(map[serialize.SUint32]*louvainNode, len(lw.communities)/lw.parallel)
+ for i := bIdx; i < eIdx; i++ {
+ commID := commIDs[i]
+ comm := lw.communities[commID]
+ newNodesInPID[commID] = &louvainNode{
+ vertex: make([]serialize.SUint32, 0, len(comm.node)),
+ neighbors: make(map[serialize.SUint32]float64),
+ kI: comm.sigmaTot,
+ commID: commID,
+ }
+ for vertex := range comm.node {
+ newNodesInPID[commID].vertex = append(newNodesInPID[commID].vertex, vertex)
+ for _, neighbor := range lw.neighborEdges[vertex] {
+ if _, ok := comm.node[neighbor]; ok {
+ newNodesInPID[commID].KIn += 1
+ continue
+ }
+ newNodesInPID[commID].neighbors[lw.firstStepCommID[neighbor]] += 1
+ }
+ lw.nodeID[vertex] = commID
+ }
+ lw.communities[commID].node = make(map[serialize.SUint32]struct{})
+ lw.communities[commID].node[commID] = struct{}{}
+ }
+ locker.Lock()
+ for i := bIdx; i < eIdx; i++ {
+ commID := commIDs[i]
+ lw.nodes[commID] = newNodesInPID[commID]
+ }
+ locker.Unlock()
+ }(i)
+ }
+ wg.Wait()
+}
+
+func (lw *LouvainWorker) genNewGraph() {
+ //gen a new graph for new step
+ //可以并行
+ newNodes := make(map[serialize.SUint32]*louvainNode, len(lw.communities))
+ locker := &sync.Mutex{}
+ commIDs := make([]serialize.SUint32, 0, len(lw.communities))
+ for commID := range lw.communities {
+ commIDs = append(commIDs, commID)
+ }
+ wg := &sync.WaitGroup{}
+ partCnt := len(commIDs)/lw.parallel + 1
+ for i := 0; i < lw.parallel; i++ {
+ wg.Add(1)
+ go func(pID int) {
+ defer wg.Done()
+ bIdx := partCnt * pID
+ if bIdx > len(commIDs) {
+ return
+ }
+ eIdx := bIdx + partCnt
+ if eIdx > len(commIDs) {
+ eIdx = len(commIDs)
+ }
+ newNodesInPID := make(map[serialize.SUint32]*louvainNode, len(lw.communities))
+ for i := bIdx; i < eIdx; i++ {
+ commID := commIDs[i]
+ comm := lw.communities[commID]
+ //合并comm.node中的所有node到newNodes
+ newNodesInPID[commID] = &louvainNode{
+ commID: commID,
+ neighbors: make(map[serialize.SUint32]float64),
+ vertex: make([]serialize.SUint32, 0)}
+ for oldNodeID := range comm.node {
+ newNodesInPID[commID].vertex = append(newNodesInPID[commID].vertex, lw.nodes[oldNodeID].vertex...)
+ newNodesInPID[commID].kI += lw.nodes[oldNodeID].kI
+ newNodesInPID[commID].KIn += lw.nodes[oldNodeID].KIn
+ for neighborID, weight := range lw.nodes[oldNodeID].neighbors {
+ if lw.nodes[neighborID].commID == commID {
+ newNodesInPID[commID].KIn += weight
+ continue
+ }
+ newNodesInPID[commID].neighbors[lw.nodes[neighborID].commID] += weight
+ }
+ }
+ for _, vertexID := range newNodesInPID[commID].vertex {
+ lw.nodeID[vertexID] = commID
+ }
+ lw.communities[commID].node = make(map[serialize.SUint32]struct{})
+ lw.communities[commID].node[commID] = struct{}{}
+ }
+ locker.Lock()
+ for i := bIdx; i < eIdx; i++ {
+ commID := commIDs[i]
+ newNodes[commID] = newNodesInPID[commID]
+ }
+ locker.Unlock()
+ }(i)
+ }
+ wg.Wait()
+ lw.nodes = newNodes
+}
+
+type LouvainMaster struct {
+ compute.MasterComputerBase
+ //阈值,总模块度值的变化是否小于阈值判断是否退出算法。
+ threshold float64
+ //前一个收敛完的迭代得到的模块度
+ prevModValue serialize.SFloat32
+ louvainStep int
+ maxStep int
+}
+
+func (lm *LouvainMaster) Init() error {
+ lm.threshold = options.GetFloat(lm.MContext.Params, "louvain.threshold")
+ lm.maxStep = options.GetInt(lm.MContext.Params, "louvain.step")
+ lm.prevModValue = math.MinInt32
+ lm.louvainStep = 1
+ return nil
+}
+
+func (lm *LouvainMaster) Compute() bool {
+ //对比模块度变化,小于阈值则提前退出
+ if lm.MContext.Step >= 2 {
+ changeNode := lm.MContext.GetValue("change_node").(serialize.SliceUint32)
+ changeComm := lm.MContext.GetValue("change_comm").(serialize.SliceUint32)
+ newNodes := make([]serialize.SUint32, 0, len(changeNode))
+ newComms := make([]serialize.SUint32, 0, len(changeComm))
+ nodes := make(map[serialize.SUint32]struct{}, len(changeNode))
+ for i, node := range changeNode {
+ if _, ok := nodes[node]; ok {
+ continue
+ }
+ nodes[node] = struct{}{}
+ newNodes = append(newNodes, node)
+ newComms = append(newComms, changeComm[i])
+ }
+ logrus.Infof("changes len:%v", len(newNodes))
+ lm.MContext.SetValue("change_node", serialize.SliceUint32(newNodes))
+ lm.MContext.SetValue("change_comm", serialize.SliceUint32(newComms))
+ if len(changeNode) == 0 {
+ lm.louvainStep++
+ lm.MContext.SetValue("update", serialize.SInt32(1))
+ } else {
+ lm.MContext.SetValue("update", serialize.SInt32(0))
+ }
+ //获取总模块度,与之前记录的总模块度相比较,判断是否退出
+ modValue := lm.MContext.GetValue("mod_value").(serialize.SFloat32)
+ if modValue <= -1 {
+ return true
+ }
+ lm.MContext.SetValue("mod_value", serialize.SFloat32(0))
+ logrus.Infof("Step:%v, Modularity:%v", lm.louvainStep, modValue)
+ if float64(modValue-lm.prevModValue) <= lm.threshold || lm.louvainStep == lm.maxStep {
+ lm.prevModValue = modValue
+ return false
+ } else {
+ lm.prevModValue = modValue
+ }
+ }
+
+ return true
+}
+
+func (lm *LouvainMaster) Statistics() map[string]any {
+ return map[string]any{
+ "modularity_in_louvain": lm.prevModValue,
+ }
+}
diff --git a/vermeer/algorithms/louvain_weighted.go b/vermeer/algorithms/louvain_weighted.go
new file mode 100644
index 00000000..a68f4199
--- /dev/null
+++ b/vermeer/algorithms/louvain_weighted.go
@@ -0,0 +1,748 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import (
+ "fmt"
+ "math"
+ "math/rand"
+ "sync"
+ "sync/atomic"
+ "vermeer/apps/common"
+ "vermeer/apps/compute"
+ "vermeer/apps/options"
+ "vermeer/apps/serialize"
+ "vermeer/apps/structure"
+
+ "github.com/sirupsen/logrus"
+)
+
+func init() {
+ Algorithms = append(Algorithms, new(LouvainWeightedMaker))
+}
+
+type LouvainWeightedMaker struct {
+ compute.AlgorithmMakerBase
+}
+
+func (lv *LouvainWeightedMaker) CreateWorkerComputer() compute.WorkerComputer {
+ return &LouvainWeightedWorker{}
+}
+
+func (lv *LouvainWeightedMaker) CreateMasterComputer() compute.MasterComputer {
+ return &LouvainWeightedMaster{}
+}
+
+func (lv *LouvainWeightedMaker) Name() string {
+ return "louvain_weighted"
+}
+
+func (lv *LouvainWeightedMaker) DataNeeded() []string {
+ return []string{compute.UseOutEdge}
+}
+
+func (lv *LouvainWeightedMaker) SupportedStatistics() map[compute.StatisticsType]struct{} {
+ return map[compute.StatisticsType]struct{}{compute.StatisticsTypeCount: {}, compute.StatisticsTypeModularity: {}}
+}
+
+type LouvainWeightedWorker struct {
+ compute.WorkerComputerBase
+ //nodeID
+ nodeID []serialize.SUint32
+ //node struct map
+ nodes map[serialize.SUint32]*louvainNode
+ //louvain step聚合后社区相关信息
+ communities map[serialize.SUint32]*community
+ //所有点的邻居节点
+ neighborEdges []serialize.MapUint32Float32
+ // 临时存储inedge节点
+ inEdges []serialize.MapUint32Float32
+ //最终的节点变化
+ node2comm []map[serialize.SUint32]serialize.SUint32
+ //误分节点导入至空社区
+ emptyComm serialize.SUint32
+ //neighbor KIin
+ //neighborCommKVInInPID []map[serialize.SUint32]float64
+ //total edge nums,带权重
+ edgeNums float64
+ //resolution : double, optional
+ //Will change the size of the communities, default to 1.
+ //represents the time described in
+ //"Laplacian Dynamics and Multiscale Modular Structure in Networks",
+ //R. Lambiotte, J.-C. Delvenne, M. Barahona
+ resolution float64
+ //parallel
+ parallel int
+ //第一阶段,以顶点为单位
+ firstStep bool
+ firstStepKI []float64
+ firstStepCommID []serialize.SUint32
+
+ // property weight
+ useProperty bool
+ propertyType structure.ValueType
+ edgeProperty string
+}
+
+func (lw *LouvainWeightedWorker) VertexValue(i uint32) serialize.MarshalAble {
+ if lw.WContext.Step == 1 {
+ return &lw.inEdges[i]
+ } else if lw.WContext.Step == 2 {
+ return &lw.neighborEdges[i]
+ }
+ if lw.WContext.Output {
+ return &lw.nodeID[i]
+ }
+ nilValue := serialize.SInt32(0)
+ return &nilValue
+}
+
+func (lw *LouvainWeightedWorker) Init() error {
+ lw.communities = make(map[serialize.SUint32]*community, lw.WContext.GraphData.Vertex.TotalVertexCount())
+
+ lw.neighborEdges = make([]serialize.MapUint32Float32, lw.WContext.GraphData.Vertex.TotalVertexCount())
+ lw.inEdges = make([]serialize.MapUint32Float32, lw.WContext.GraphData.Vertex.TotalVertexCount())
+
+ lw.resolution = options.GetFloat(lw.WContext.Params, "louvain.resolution")
+ lw.parallel = lw.WContext.Parallel
+ if lw.parallel <= 0 {
+ logrus.Infof("parallel value must be larger than 0, get: %v, set to defalut value :1", lw.parallel)
+ lw.parallel = 1
+ }
+ lw.node2comm = make([]map[serialize.SUint32]serialize.SUint32, lw.parallel)
+ for i := range lw.node2comm {
+ lw.node2comm[i] = make(map[serialize.SUint32]serialize.SUint32, lw.WContext.GraphData.Vertex.TotalVertexCount()/uint32(lw.parallel))
+ }
+ //lw.neighborCommKVInInPID = make([]map[serialize.SUint32]float64, lw.parallel)
+ lw.WContext.CreateValue("change_node", compute.ValueTypeSliceUint32, compute.CValueActionAggregate)
+ lw.WContext.SetValue("change_node", serialize.SliceUint32{})
+ lw.WContext.CreateValue("change_comm", compute.ValueTypeSliceUint32, compute.CValueActionAggregate)
+ lw.WContext.SetValue("change_comm", serialize.SliceUint32{})
+ lw.WContext.CreateValue("mod_value", compute.ValueTypeFloat32, compute.CValueActionAggregate)
+ lw.WContext.SetValue("mod_value", serialize.SFloat32(0))
+
+ lw.WContext.CreateValue("update", compute.ValueTypeInt32, compute.CValueActionAggregate)
+ lw.WContext.SetValue("update", serialize.SInt32(0))
+
+ lw.emptyComm = serialize.SUint32(lw.WContext.GraphData.Vertex.TotalVertexCount() + 1)
+ lw.firstStep = true
+
+ // load edge weight
+ lw.edgeProperty = options.GetString(lw.WContext.Params, "louvain.edge_weight_property")
+ if len(lw.edgeProperty) > 0 {
+ vType, ok := lw.WContext.GraphData.InEdgesProperty.GetValueType(lw.edgeProperty)
+ if !ok {
+ logrus.Errorf("unknown edge weighted property:%v", lw.edgeProperty)
+ return fmt.Errorf("unknown edge weighted property:%v", lw.edgeProperty)
+ }
+ switch vType {
+ case structure.ValueTypeInt32, structure.ValueTypeFloat32:
+ lw.propertyType = vType
+ case structure.ValueTypeString:
+ logrus.Errorf("illegal edge weighted property type:%v", lw.edgeProperty)
+ return fmt.Errorf("illegal edge weighted property type:%v", lw.edgeProperty)
+ }
+ lw.useProperty = true
+ }
+ return nil
+}
+
+func (lw *LouvainWeightedWorker) BeforeStep() {
+ if lw.WContext.Step == 3 {
+ lw.inEdges = nil
+
+ lw.firstStepKI = make([]float64, lw.WContext.GraphData.Vertex.TotalVertexCount())
+ lw.firstStepCommID = make([]serialize.SUint32, lw.WContext.GraphData.Vertex.TotalVertexCount())
+ for vertexID, edges := range lw.neighborEdges {
+ if len(edges) == 0 {
+ continue
+ }
+ var wgtSum float64
+ for _, wgt := range edges {
+ wgtSum += float64(wgt)
+ }
+ lw.edgeNums += wgtSum
+ lw.communities[serialize.SUint32(vertexID)] = &community{node: map[serialize.SUint32]struct{}{serialize.SUint32(vertexID): {}}}
+ lw.communities[serialize.SUint32(vertexID)].sigmaTot = wgtSum
+ lw.firstStepKI[vertexID] = wgtSum
+ lw.firstStepCommID[vertexID] = serialize.SUint32(vertexID)
+ }
+ lw.resolution /= lw.edgeNums
+ logrus.Infof("edge nums:%v", lw.edgeNums)
+ logrus.Infof("resolution:%v", lw.resolution)
+ lw.WContext.SetValue("mod_value", serialize.SFloat32(-1))
+ } else if lw.WContext.Step > 3 {
+ changeNode := lw.WContext.GetValue("change_node").(serialize.SliceUint32)
+ changeComm := lw.WContext.GetValue("change_comm").(serialize.SliceUint32)
+ //changes := make(map[serialize.SUint32]serialize.SUint32)
+ currCommIDs := make(map[serialize.SUint32]struct{})
+ moveToEmpty := make([]serialize.SUint32, 0)
+ for i, node := range changeNode {
+ if changeComm[i] == lw.emptyComm {
+ moveToEmpty = append(moveToEmpty, node)
+ continue
+ }
+ var currCommID serialize.SUint32
+ var ki float64
+ if lw.firstStep {
+ currCommID = lw.firstStepCommID[node]
+ ki = lw.firstStepKI[node]
+ lw.firstStepCommID[node] = changeComm[i]
+ } else {
+ currCommID = lw.nodes[node].commID
+ ki = lw.nodes[node].kI
+ lw.nodes[node].commID = changeComm[i]
+ }
+ currCommIDs[currCommID] = struct{}{}
+ delete(lw.communities[currCommID].node, node)
+ lw.communities[currCommID].sigmaTot -= ki
+ lw.communities[changeComm[i]].node[node] = struct{}{}
+ lw.communities[changeComm[i]].sigmaTot += ki
+ }
+ if len(moveToEmpty) > 0 {
+ logrus.Infof("move to empty node len:%v", len(moveToEmpty))
+ emptyComms := make([]serialize.SUint32, len(moveToEmpty))
+ idx := 0
+ for i := 0; i < int(lw.WContext.GraphData.Vertex.TotalVertexCount()); i++ {
+ if idx == len(moveToEmpty) {
+ break
+ }
+ if comm, ok := lw.communities[serialize.SUint32(i)]; ok {
+ if len(comm.node) == 0 {
+ emptyComms[idx] = serialize.SUint32(i)
+ idx++
+ }
+ }
+ }
+ MoveOutComm := make(map[serialize.SUint32]int)
+ for _, node := range moveToEmpty {
+ var currCommID serialize.SUint32
+ if lw.firstStep {
+ currCommID = lw.firstStepCommID[node]
+ } else {
+ currCommID = lw.nodes[node].commID
+ }
+ currCommIDs[currCommID] = struct{}{}
+ MoveOutComm[currCommID] += 1
+ }
+ alreadyMoveOutComm := make(map[serialize.SUint32]int, len(MoveOutComm))
+ for i, node := range moveToEmpty {
+ var ki float64
+ var currCommID serialize.SUint32
+ if lw.firstStep {
+ currCommID = lw.firstStepCommID[node]
+ } else {
+ currCommID = lw.nodes[node].commID
+ }
+ alreadyMoveOutComm[currCommID] += 1
+ if alreadyMoveOutComm[currCommID] > MoveOutComm[currCommID]/2 && MoveOutComm[currCommID] > 1 {
+ continue
+ }
+ if lw.firstStep {
+ ki = lw.firstStepKI[node]
+ lw.firstStepCommID[node] = emptyComms[i]
+ } else {
+ ki = lw.nodes[node].kI
+ lw.nodes[node].commID = emptyComms[i]
+ }
+ delete(lw.communities[currCommID].node, node)
+ lw.communities[currCommID].sigmaTot -= ki
+ lw.communities[emptyComms[i]].node[node] = struct{}{}
+ lw.communities[emptyComms[i]].sigmaTot += ki
+ }
+ }
+ update := lw.WContext.GetValue("update").(serialize.SInt32)
+ if update > 0 {
+ lw.deleteEmptyComm()
+ if lw.firstStep {
+ lw.firstStep = false
+ //初始化node
+ lw.initLouvainNode()
+ //free memory
+ lw.firstStepCommID = nil
+ lw.firstStepKI = nil
+ lw.neighborEdges = nil
+ } else {
+ //生成新图
+ lw.genNewGraph()
+ }
+ lw.WContext.SetValue("mod_value", serialize.SFloat32(lw.calModularity()))
+ } else {
+ if rand.Float32() < 0.5 {
+ lw.optimizeMem(currCommIDs)
+ }
+ lw.WContext.SetValue("mod_value", serialize.SFloat32(-1))
+ }
+ lw.node2comm = make([]map[serialize.SUint32]serialize.SUint32, lw.parallel)
+ for i := range lw.node2comm {
+ lw.node2comm[i] = make(map[serialize.SUint32]serialize.SUint32, len(changeNode)/lw.parallel)
+ }
+ for nodeID := range lw.nodes {
+ lw.nodes[nodeID].once = 0
+ }
+ }
+ logrus.Infof("communities num:%v", len(lw.communities))
+}
+
+func (lw *LouvainWeightedWorker) Compute(vertexID uint32, pID int) {
+ //step 1:同步所有顶点的邻边
+ vertID := vertexID - lw.WContext.GraphData.VertIDStart
+ if len(lw.WContext.GraphData.Edges.GetInEdges(vertID))+len(lw.WContext.GraphData.Edges.GetOutEdges(vertID)) == 0 {
+ return
+ }
+ if lw.WContext.Step == 1 {
+ // scatter inedge and weight
+ lw.inEdges[vertexID] = make(serialize.MapUint32Float32, len(lw.WContext.GraphData.Edges.GetInEdges(vertID)))
+ for idx, edge := range lw.WContext.GraphData.Edges.GetInEdges(vertID) {
+ // trim self loop
+ if edge == serialize.SUint32(vertexID) {
+ continue
+ }
+ var weight serialize.SFloat32 = 1
+ if lw.useProperty {
+ switch lw.propertyType {
+ case structure.ValueTypeInt32:
+ weight = serialize.SFloat32(lw.WContext.GraphData.InEdgesProperty.GetInt32Value(lw.edgeProperty, vertID, uint32(idx)))
+ case structure.ValueTypeFloat32:
+ weight = lw.WContext.GraphData.InEdgesProperty.GetFloat32Value(lw.edgeProperty, vertID, uint32(idx))
+ }
+
+ }
+ lw.inEdges[vertexID][edge] += weight
+ }
+ } else if lw.WContext.Step == 2 {
+ // get outedge weight
+ lw.neighborEdges[vertexID] = make(serialize.MapUint32Float32, len(lw.inEdges[vertexID]))
+ trimMap := make(map[serialize.SUint32]struct{})
+ trimMap[serialize.SUint32(vertexID)] = struct{}{}
+ for edge, weight := range lw.inEdges[vertexID] {
+ lw.neighborEdges[vertexID][edge] += weight
+ }
+ for _, edge := range lw.WContext.GraphData.Edges.GetOutEdges(vertID) {
+ if _, ok := trimMap[edge]; ok {
+ continue
+ }
+ trimMap[edge] = struct{}{}
+ wgt := lw.inEdges[edge][serialize.SUint32(vertexID)]
+ lw.neighborEdges[vertexID][edge] += wgt
+ }
+ } else {
+ if lw.firstStep {
+ //以vertex为基本单元计算
+ currCommID := lw.firstStepCommID[vertexID]
+ kI := lw.firstStepKI[vertexID]
+
+ //neighborCommKIin 计算neighbor社区的KIin
+ neighborCommKVInInPID := make(map[serialize.SUint32]float64, len(lw.neighborEdges[vertexID]))
+
+ for neighbor, weight := range lw.neighborEdges[vertexID] {
+ neighborCommID := lw.firstStepCommID[neighbor]
+ neighborCommKVInInPID[neighborCommID] += float64(weight)
+ }
+
+ var maxDeltaQ float64
+ targetCommID := currCommID
+ for neighborCommID, kVIn := range neighborCommKVInInPID {
+ sigmaTot := lw.communities[neighborCommID].sigmaTot
+ if currCommID == neighborCommID {
+ sigmaTot -= kI
+ }
+ commDeltaQ := lw.calDeltaQ(kVIn, sigmaTot, kI)
+ if commDeltaQ > maxDeltaQ {
+ targetCommID = neighborCommID
+ maxDeltaQ = commDeltaQ
+ }
+ }
+ if maxDeltaQ == 0 && len(lw.communities[currCommID].node) > 1 {
+ lw.node2comm[pID][serialize.SUint32(vertexID)] = lw.emptyComm
+ }
+ if targetCommID >= currCommID {
+ return
+ }
+ lw.node2comm[pID][serialize.SUint32(vertexID)] = targetCommID
+ } else {
+ nodeID := lw.nodeID[vertexID]
+ if lw.nodes[nodeID] == nil || atomic.LoadInt32(&lw.nodes[nodeID].once) > 0 {
+ return
+ }
+ atomic.AddInt32(&lw.nodes[nodeID].once, 1)
+ currCommID := lw.nodes[nodeID].commID
+ kI := lw.nodes[nodeID].kI
+
+ //neighborCommKIin 计算neighbor社区的KIin
+ neighborCommKVInInPID := make(map[serialize.SUint32]float64, len(lw.nodes[nodeID].neighbors))
+ for neighbor, weight := range lw.nodes[nodeID].neighbors {
+ neighborCommID := lw.nodes[neighbor].commID
+ neighborCommKVInInPID[neighborCommID] += weight
+ }
+
+ var maxDeltaQ float64
+ targetCommID := currCommID
+ for neighborCommID, kVIn := range neighborCommKVInInPID {
+ sigmaTot := lw.communities[neighborCommID].sigmaTot
+ if currCommID == neighborCommID {
+ sigmaTot -= kI
+ }
+ commDeltaQ := lw.calDeltaQ(kVIn, sigmaTot, kI)
+ if commDeltaQ > maxDeltaQ {
+ targetCommID = neighborCommID
+ maxDeltaQ = commDeltaQ
+ }
+ }
+
+ if maxDeltaQ == 0 && len(lw.communities[currCommID].node) > 1 {
+ lw.node2comm[pID][nodeID] = lw.emptyComm
+ }
+ if targetCommID >= currCommID {
+ return
+ }
+ lw.node2comm[pID][nodeID] = targetCommID
+ }
+ }
+}
+
+func (lw *LouvainWeightedWorker) AfterStep() {
+ if lw.WContext.Step >= 3 {
+ changeNode := make([]serialize.SUint32, 0, len(lw.node2comm))
+ changeComm := make([]serialize.SUint32, 0, len(lw.node2comm))
+ for _, node2comm := range lw.node2comm {
+ for node, comm := range node2comm {
+ changeNode = append(changeNode, node)
+ changeComm = append(changeComm, comm)
+ }
+ }
+ //logrus.Infof("changenode:%v,changecomm:%v", changeNode, changeComm)
+ lw.WContext.SetValue("change_node", serialize.SliceUint32(changeNode))
+ lw.WContext.SetValue("change_comm", serialize.SliceUint32(changeComm))
+ }
+}
+
+func (lw *LouvainWeightedWorker) OutputValueType() string {
+ return common.HgValueTypeInt
+}
+
+func (lw *LouvainWeightedWorker) optimizeMem(currCommIDs map[serialize.SUint32]struct{}) {
+ //优化内存
+ commIDs := make([]serialize.SUint32, 0, len(currCommIDs))
+ for commID := range currCommIDs {
+ commIDs = append(commIDs, commID)
+ }
+ partCnt := len(commIDs)/lw.parallel + 1
+ wg := &sync.WaitGroup{}
+ for i := 0; i < lw.parallel; i++ {
+ wg.Add(1)
+ go func(pID int) {
+ defer wg.Done()
+ bIdx := partCnt * pID
+ if bIdx > len(commIDs) {
+ return
+ }
+ eIdx := bIdx + partCnt
+ if eIdx > len(commIDs) {
+ eIdx = len(commIDs)
+ }
+ for i := bIdx; i < eIdx; i++ {
+ commID := commIDs[i]
+ newNodes := make(map[serialize.SUint32]struct{}, len(lw.communities[commID].node))
+ for node := range lw.communities[commID].node {
+ newNodes[node] = struct{}{}
+ }
+ lw.communities[commID].node = newNodes
+ }
+ }(i)
+ }
+ wg.Wait()
+ //for commID := range currCommIDs {
+ // newNodes := make(map[serialize.SUint32]struct{}, len(lw.communities[commID].node))
+ // for node := range lw.communities[commID].node {
+ // newNodes[node] = struct{}{}
+ // }
+ // lw.communities[commID].node = newNodes
+ //}
+}
+
+func (lw *LouvainWeightedWorker) deleteEmptyComm() {
+ //删除空的社区
+ for commID, comm := range lw.communities {
+ if len(comm.node) == 0 {
+ delete(lw.communities, commID)
+ }
+ }
+ newComm := make(map[serialize.SUint32]*community, len(lw.communities))
+ for commID, comm := range lw.communities {
+ newComm[commID] = comm
+ }
+ lw.communities = newComm
+}
+
+func (lw *LouvainWeightedWorker) calDeltaQ(kVIn, sigmaTot, kI float64) float64 {
+ //DeltaQ = k_v_in - tot * k_v / m
+ //各元素物理意义:
+ //k_v_in: 当前点指向目标点所在社区的边权值之和
+ //tot: 目标点所在社区内边外边权重之和(如果当前点和目标点处在同一个社区,去要减掉一个k_v)
+ //k_v: 当前点内外度之和
+ //m: 全图边权重之和,已处理在resolution之内
+ return kVIn - lw.resolution*sigmaTot*kI
+}
+
+func (lw *LouvainWeightedWorker) calModularity() float64 {
+ //模块度计算,可以实现并行计算
+ //.. math::
+ //Q = \sum_{c=1}^{n}
+ //\left[ \frac{L_c}{m} - \gamma\left( \frac{k_c}{2m} \right) ^2 \right]
+ //
+ //where the sum iterates over all communities $c$, $m$ is the number of edges,
+ //$L_c$ is the number of intra-community links for community $c$,
+ //$k_c$ is the sum of degrees of the nodes in community $c$,
+ //and $\gamma$ is the resolution parameter.
+ var mod float64
+ commIDs := make([]serialize.SUint32, 0, len(lw.communities))
+ for commID := range lw.communities {
+ commIDs = append(commIDs, commID)
+ }
+ wg := &sync.WaitGroup{}
+ locker := &sync.Mutex{}
+ partCnt := len(commIDs)/lw.parallel + 1
+ for i := 0; i < lw.parallel; i++ {
+ wg.Add(1)
+ go func(pID int) {
+ defer wg.Done()
+ bIdx := partCnt * pID
+ if bIdx > len(commIDs) {
+ return
+ }
+ eIdx := bIdx + partCnt
+ if eIdx > len(commIDs) {
+ eIdx = len(commIDs)
+ }
+ var modInPID float64
+ for i := bIdx; i < eIdx; i++ {
+ commID := commIDs[i]
+ if int(commID)%lw.WContext.Workers == lw.WContext.WorkerIdx {
+ comm := lw.communities[commID]
+ var commInDegree float64
+ for nodeID := range comm.node {
+ commInDegree += lw.nodes[nodeID].KIn
+ for neighborID, weight := range lw.nodes[nodeID].neighbors {
+ if lw.nodes[neighborID].commID == commID {
+ commInDegree += weight
+ }
+ }
+ }
+ modInPID += commInDegree/lw.edgeNums - (comm.sigmaTot/lw.edgeNums)*comm.sigmaTot*lw.resolution
+ }
+ }
+ locker.Lock()
+ mod += modInPID
+ locker.Unlock()
+ }(i)
+ }
+ wg.Wait()
+ return mod
+}
+
+func (lw *LouvainWeightedWorker) initLouvainNode() {
+ //gen a new graph for new step
+ //可以并行
+ lw.nodes = make(map[serialize.SUint32]*louvainNode, len(lw.communities))
+ lw.nodeID = make([]serialize.SUint32, lw.WContext.GraphData.Vertex.TotalVertexCount())
+ for i := range lw.nodeID {
+ lw.nodeID[i] = serialize.SUint32(i)
+ }
+ locker := &sync.Mutex{}
+ commIDs := make([]serialize.SUint32, 0, len(lw.communities))
+ for commID := range lw.communities {
+ commIDs = append(commIDs, commID)
+ }
+ wg := &sync.WaitGroup{}
+ partCnt := len(commIDs)/lw.parallel + 1
+ for i := 0; i < lw.parallel; i++ {
+ wg.Add(1)
+ go func(pID int) {
+ defer wg.Done()
+ bIdx := partCnt * pID
+ if bIdx > len(commIDs) {
+ return
+ }
+ eIdx := bIdx + partCnt
+ if eIdx > len(commIDs) {
+ eIdx = len(commIDs)
+ }
+ newNodesInPID := make(map[serialize.SUint32]*louvainNode, len(lw.communities)/lw.parallel)
+ for i := bIdx; i < eIdx; i++ {
+ commID := commIDs[i]
+ comm := lw.communities[commID]
+ newNodesInPID[commID] = &louvainNode{
+ vertex: make([]serialize.SUint32, 0, len(comm.node)),
+ neighbors: make(map[serialize.SUint32]float64),
+ kI: comm.sigmaTot,
+ commID: commID,
+ }
+ for vertex := range comm.node {
+ newNodesInPID[commID].vertex = append(newNodesInPID[commID].vertex, vertex)
+ for neighbor, weight := range lw.neighborEdges[vertex] {
+ if _, ok := comm.node[neighbor]; ok {
+ newNodesInPID[commID].KIn += float64(weight)
+ continue
+ }
+ newNodesInPID[commID].neighbors[lw.firstStepCommID[neighbor]] += float64(weight)
+ }
+ lw.nodeID[vertex] = commID
+ }
+ lw.communities[commID].node = make(map[serialize.SUint32]struct{})
+ lw.communities[commID].node[commID] = struct{}{}
+ }
+ locker.Lock()
+ for i := bIdx; i < eIdx; i++ {
+ commID := commIDs[i]
+ lw.nodes[commID] = newNodesInPID[commID]
+ }
+ locker.Unlock()
+ }(i)
+ }
+ wg.Wait()
+}
+
+func (lw *LouvainWeightedWorker) genNewGraph() {
+ //gen a new graph for new step
+ //可以并行
+ newNodes := make(map[serialize.SUint32]*louvainNode, len(lw.communities))
+ locker := &sync.Mutex{}
+ commIDs := make([]serialize.SUint32, 0, len(lw.communities))
+ for commID := range lw.communities {
+ commIDs = append(commIDs, commID)
+ }
+ wg := &sync.WaitGroup{}
+ partCnt := len(commIDs)/lw.parallel + 1
+ for i := 0; i < lw.parallel; i++ {
+ wg.Add(1)
+ go func(pID int) {
+ defer wg.Done()
+ bIdx := partCnt * pID
+ if bIdx > len(commIDs) {
+ return
+ }
+ eIdx := bIdx + partCnt
+ if eIdx > len(commIDs) {
+ eIdx = len(commIDs)
+ }
+ newNodesInPID := make(map[serialize.SUint32]*louvainNode, len(lw.communities))
+ for i := bIdx; i < eIdx; i++ {
+ commID := commIDs[i]
+ comm := lw.communities[commID]
+ //合并comm.node中的所有node到newNodes
+ newNodesInPID[commID] = &louvainNode{
+ commID: commID,
+ neighbors: make(map[serialize.SUint32]float64),
+ vertex: make([]serialize.SUint32, 0)}
+ for oldNodeID := range comm.node {
+ newNodesInPID[commID].vertex = append(newNodesInPID[commID].vertex, lw.nodes[oldNodeID].vertex...)
+ newNodesInPID[commID].kI += lw.nodes[oldNodeID].kI
+ newNodesInPID[commID].KIn += lw.nodes[oldNodeID].KIn
+ for neighborID, weight := range lw.nodes[oldNodeID].neighbors {
+ if lw.nodes[neighborID].commID == commID {
+ newNodesInPID[commID].KIn += weight
+ continue
+ }
+ newNodesInPID[commID].neighbors[lw.nodes[neighborID].commID] += weight
+ }
+ }
+ for _, vertexID := range newNodesInPID[commID].vertex {
+ lw.nodeID[vertexID] = commID
+ }
+ lw.communities[commID].node = make(map[serialize.SUint32]struct{})
+ lw.communities[commID].node[commID] = struct{}{}
+ }
+ locker.Lock()
+ for i := bIdx; i < eIdx; i++ {
+ commID := commIDs[i]
+ newNodes[commID] = newNodesInPID[commID]
+ }
+ locker.Unlock()
+ }(i)
+ }
+ wg.Wait()
+ lw.nodes = newNodes
+}
+
+type LouvainWeightedMaster struct {
+ compute.MasterComputerBase
+ //阈值,总模块度值的变化是否小于阈值判断是否退出算法。
+ threshold float64
+ //前一个收敛完的迭代得到的模块度
+ prevModValue serialize.SFloat32
+ louvainStep int
+ maxStep int
+}
+
+func (lm *LouvainWeightedMaster) Init() error {
+ lm.threshold = options.GetFloat(lm.MContext.Params, "louvain.threshold")
+ lm.maxStep = options.GetInt(lm.MContext.Params, "louvain.step")
+ lm.prevModValue = math.MinInt32
+ lm.louvainStep = 1
+ return nil
+}
+
+func (lm *LouvainWeightedMaster) Compute() bool {
+ //对比模块度变化,小于阈值则提前退出
+ if lm.MContext.Step >= 3 {
+ changeNode := lm.MContext.GetValue("change_node").(serialize.SliceUint32)
+ changeComm := lm.MContext.GetValue("change_comm").(serialize.SliceUint32)
+ newNodes := make([]serialize.SUint32, 0, len(changeNode))
+ newComms := make([]serialize.SUint32, 0, len(changeComm))
+ nodes := make(map[serialize.SUint32]struct{}, len(changeNode))
+ for i, node := range changeNode {
+ if _, ok := nodes[node]; ok {
+ continue
+ }
+ nodes[node] = struct{}{}
+ newNodes = append(newNodes, node)
+ newComms = append(newComms, changeComm[i])
+ }
+ logrus.Infof("changes len:%v", len(newNodes))
+ lm.MContext.SetValue("change_node", serialize.SliceUint32(newNodes))
+ lm.MContext.SetValue("change_comm", serialize.SliceUint32(newComms))
+ if len(changeNode) == 0 {
+ lm.louvainStep++
+ lm.MContext.SetValue("update", serialize.SInt32(1))
+ } else {
+ lm.MContext.SetValue("update", serialize.SInt32(0))
+ }
+ //获取总模块度,与之前记录的总模块度相比较,判断是否退出
+ modValue := lm.MContext.GetValue("mod_value").(serialize.SFloat32)
+ if modValue <= -1 {
+ return true
+ }
+ lm.MContext.SetValue("mod_value", serialize.SFloat32(0))
+ logrus.Infof("Step:%v, Modularity:%v", lm.louvainStep, modValue)
+ if float64(modValue-lm.prevModValue) <= lm.threshold || lm.louvainStep >= lm.maxStep {
+ lm.prevModValue = modValue
+ return false
+ } else {
+ lm.prevModValue = modValue
+ }
+ }
+
+ return true
+}
+
+func (lm *LouvainWeightedMaster) Statistics() map[string]any {
+ return map[string]any{
+ "modularity_in_louvain_weighted": lm.prevModValue,
+ }
+}
diff --git a/vermeer/algorithms/lpa.go b/vermeer/algorithms/lpa.go
new file mode 100644
index 00000000..feec256e
--- /dev/null
+++ b/vermeer/algorithms/lpa.go
@@ -0,0 +1,286 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import (
+ "fmt"
+ "strings"
+ "vermeer/apps/common"
+ "vermeer/apps/compute"
+ "vermeer/apps/options"
+ "vermeer/apps/serialize"
+ "vermeer/apps/structure"
+
+ "github.com/sirupsen/logrus"
+)
+
+func init() {
+ Algorithms = append(Algorithms, new(LpaMaker))
+}
+
+type LpaMaker struct {
+ compute.AlgorithmMakerBase
+}
+
+func (lm *LpaMaker) CreateWorkerComputer() compute.WorkerComputer {
+ return &LpaWorker{}
+}
+
+func (lm *LpaMaker) CreateMasterComputer() compute.MasterComputer {
+ return &LpaMaster{}
+}
+
+func (lm *LpaMaker) Name() string {
+ return "lpa"
+}
+
+func (lm *LpaMaker) DataNeeded() []string {
+ return []string{compute.UseOutEdge}
+}
+func (lm *LpaMaker) SupportedStatistics() map[compute.StatisticsType]struct{} {
+ return map[compute.StatisticsType]struct{}{compute.StatisticsTypeCount: {}, compute.StatisticsTypeModularity: {}}
+}
+
+type LpaWorker struct {
+ compute.WorkerComputerBase
+ grandpaLabels []serialize.SUint32
+ oldLabels []serialize.SUint32
+ newLabels []serialize.SUint32
+ grandpaDiffs []serialize.SInt32
+ diffSum []serialize.SInt32
+ useProperty bool
+ propertyType structure.ValueType
+ vertexProperty string
+ compareOption int
+ updateMethod int
+}
+
+func (lw *LpaWorker) VertexValue(i uint32) serialize.MarshalAble {
+ return &lw.newLabels[i]
+}
+
+const (
+ UpdateMethodSync = "sync"
+ UpdateMethodSemiSync = "semi_sync"
+)
+
+const (
+ CompareVetexID = "id"
+ CompareVertexOriginID = "origin"
+)
+
+func (lw *LpaWorker) Init() error {
+ // 初始化label为自增id
+ lw.oldLabels = make([]serialize.SUint32, lw.WContext.GraphData.Vertex.TotalVertexCount())
+ lw.newLabels = make([]serialize.SUint32, lw.WContext.GraphData.Vertex.TotalVertexCount())
+ lw.grandpaLabels = make([]serialize.SUint32, lw.WContext.GraphData.Vertex.TotalVertexCount())
+ for id := range lw.oldLabels {
+ lw.oldLabels[id] = serialize.SUint32(id)
+ lw.newLabels[id] = serialize.SUint32(id)
+ }
+
+ lw.diffSum = make([]serialize.SInt32, lw.WContext.Parallel)
+ lw.grandpaDiffs = make([]serialize.SInt32, lw.WContext.Parallel)
+
+ lw.WContext.CreateValue("diff_sum", compute.ValueTypeInt32, compute.CValueActionAggregate)
+ lw.WContext.SetValue("diff_sum", serialize.SInt32(0))
+ lw.WContext.CreateValue("grandpa_diff_sum", compute.ValueTypeInt32, compute.CValueActionAggregate)
+ lw.WContext.SetValue("grandpa_diff_sum", serialize.SInt32(0))
+
+ switch options.GetString(lw.WContext.Params, "lpa.compare_option") {
+ case CompareVetexID:
+ lw.compareOption = 0
+ case CompareVertexOriginID:
+ lw.compareOption = 1
+ default:
+ logrus.Errorf("illegal compare option:%v, use id/origin", options.GetString(lw.WContext.Params, "lpa.compare_option"))
+ return fmt.Errorf("illegal compare option:%v, use id/origin", options.GetString(lw.WContext.Params, "lpa.compare_option"))
+ }
+
+ switch options.GetString(lw.WContext.Params, "lpa.update_method") {
+ case UpdateMethodSync:
+ lw.updateMethod = 0
+ case UpdateMethodSemiSync:
+ lw.updateMethod = 1
+ default:
+ logrus.Errorf("illegal update method:%v, use sync/semi_sync", options.GetString(lw.WContext.Params, "lpa.update_method"))
+ return fmt.Errorf("illegal update method:%v, use sync/semi_sync", options.GetString(lw.WContext.Params, "lpa.update_method"))
+ }
+
+ lw.vertexProperty = options.GetString(lw.WContext.Params, "lpa.vertex_weight_property")
+ if len(lw.vertexProperty) > 0 {
+ vType, ok := lw.WContext.GraphData.VertexProperty.GetValueType(lw.vertexProperty)
+ if !ok {
+ logrus.Errorf("unknown vertex weighted property:%v", lw.vertexProperty)
+ return fmt.Errorf("unknown vertex weighted property:%v", lw.vertexProperty)
+ }
+ switch vType {
+ case structure.ValueTypeInt32, structure.ValueTypeFloat32:
+ lw.propertyType = vType
+ case structure.ValueTypeString:
+ logrus.Errorf("illegal vertex weighted property type:%v", lw.vertexProperty)
+ return fmt.Errorf("illegal vertex weighted property type:%v", lw.vertexProperty)
+ }
+ lw.useProperty = true
+ }
+ return nil
+}
+
+func (lw *LpaWorker) BeforeStep() {
+ for i := range lw.diffSum {
+ lw.diffSum[i] = 0
+ lw.grandpaDiffs[i] = 0
+ }
+}
+
+func (lw *LpaWorker) inSelfWorker(vertexID uint32) bool {
+ return vertexID >= lw.WContext.GraphData.VertIDStart && vertexID < (lw.WContext.GraphData.VertexCount+lw.WContext.GraphData.VertIDStart)
+}
+
+func (lw *LpaWorker) Compute(vertexId uint32, pId int) {
+ _ = pId
+ vertexIdx := vertexId - lw.WContext.GraphData.VertIDStart
+
+ //count key:自增label value:frequency
+ count := make(map[serialize.SUint32]float64)
+ //if lw.WContext.GraphData.BothEdges != nil {
+ // for _, node := range lw.WContext.GraphData.BothEdges[vertexIdx] {
+ // count[lw.oldLabels[node]]++
+ // }
+ //} else {
+ for _, inNode := range lw.WContext.GraphData.Edges.GetInEdges(vertexIdx) {
+ if lw.updateMethod == 1 && lw.inSelfWorker(uint32(inNode)) {
+ count[lw.newLabels[inNode]]++
+ } else {
+ count[lw.oldLabels[inNode]]++
+ }
+ }
+ for _, outNode := range lw.WContext.GraphData.Edges.GetOutEdges(vertexIdx) {
+ if lw.updateMethod == 1 && lw.inSelfWorker(uint32(outNode)) {
+ count[lw.newLabels[outNode]]++
+ } else {
+ count[lw.oldLabels[outNode]]++
+ }
+ }
+ //}
+ if lw.useProperty {
+ for node := range count {
+ switch lw.propertyType {
+ case structure.ValueTypeFloat32:
+ count[node] *= float64(lw.WContext.GraphData.VertexProperty.GetFloat32Value(lw.vertexProperty, vertexId))
+ case structure.ValueTypeInt32:
+ count[node] *= float64(lw.WContext.GraphData.VertexProperty.GetInt32Value(lw.vertexProperty, vertexId))
+ }
+ }
+ }
+
+ //得到maxCount
+ maxCount := float64(0)
+ for _, labelCount := range count {
+ if maxCount < labelCount {
+ maxCount = labelCount
+ }
+ }
+
+ //得到满足maxCount的所有自增label
+ candidate := make([]serialize.SUint32, 0)
+ for originLabel, labelCount := range count {
+ if labelCount == maxCount {
+ candidate = append(candidate, originLabel)
+ }
+ }
+ if len(candidate) == 0 {
+ return
+ }
+
+ // // 如果原label在候选中,则不更新
+ // for _, can := range candidate {
+ // if can == lw.oldLabels[vertexId] {
+ // lw.newLabels[vertexId] = can
+ // return
+ // }
+ // }
+
+ // 比较originLabel
+ if lw.compareOption == 0 {
+ minLabel := candidate[0]
+ for _, candiLabel := range candidate {
+ originLabel := candiLabel
+ if originLabel < minLabel {
+ minLabel = originLabel
+ }
+ }
+ lw.newLabels[vertexId] = minLabel
+ } else if lw.compareOption == 1 {
+ minLabel := lw.WContext.GraphData.Vertex.GetVertex(uint32(candidate[0])).ID
+ for _, candiLabel := range candidate {
+ originLabel := lw.WContext.GraphData.Vertex.GetVertex(uint32(candiLabel)).ID
+ if strings.Compare(originLabel, minLabel) < 0 {
+ minLabel = originLabel
+ }
+ }
+ vertexIndex, ok := lw.WContext.GraphData.Vertex.GetVertexIndex(minLabel)
+ if !ok {
+ logrus.Errorf("unknown vertex %v", minLabel)
+ }
+ lw.newLabels[vertexId] = serialize.SUint32(vertexIndex)
+ }
+ // }
+ if lw.newLabels[vertexId] != lw.oldLabels[vertexId] {
+ lw.diffSum[pId]++
+ }
+ if lw.newLabels[vertexId] != lw.grandpaLabels[vertexId] {
+ lw.grandpaDiffs[pId]++
+ }
+}
+
+func (lw *LpaWorker) AfterStep() {
+ // 更新oldLabels
+ copy(lw.grandpaLabels, lw.oldLabels)
+ copy(lw.oldLabels, lw.newLabels)
+
+ var diffSum serialize.SInt32
+ for _, diff := range lw.diffSum {
+ diffSum += diff
+ }
+ lw.WContext.SetValue("diff_sum", diffSum)
+
+ var grandpaDiffSum serialize.SInt32
+ for _, diff := range lw.grandpaDiffs {
+ grandpaDiffSum += diff
+ }
+ lw.WContext.SetValue("grandpa_diff_sum", grandpaDiffSum)
+}
+
+func (lw *LpaWorker) OutputValueType() string {
+ return common.HgValueTypeInt
+}
+
+type LpaMaster struct {
+ compute.MasterComputerBase
+}
+
+func (wcm *LpaMaster) Compute() bool {
+ diffSum := wcm.MContext.GetValue("diff_sum").(serialize.SInt32)
+ logrus.Infof("different sum: %v", diffSum)
+
+ grandpaDiffs := wcm.MContext.GetValue("grandpa_diff_sum").(serialize.SInt32)
+ logrus.Infof("grandpa different sum: %v", grandpaDiffs)
+
+ return diffSum != 0 && grandpaDiffs != 0
+}
diff --git a/vermeer/algorithms/pagerank.go b/vermeer/algorithms/pagerank.go
new file mode 100644
index 00000000..3e410b70
--- /dev/null
+++ b/vermeer/algorithms/pagerank.go
@@ -0,0 +1,192 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import (
+ "fmt"
+ "math"
+ "vermeer/apps/common"
+ "vermeer/apps/compute"
+ "vermeer/apps/options"
+ "vermeer/apps/serialize"
+ "vermeer/apps/structure"
+
+ "github.com/sirupsen/logrus"
+)
+
+func init() {
+ Algorithms = append(Algorithms, new(PageRankMaker))
+}
+
+type PageRankMaker struct {
+ compute.AlgorithmMakerBase
+}
+
+func (prm *PageRankMaker) CreateWorkerComputer() compute.WorkerComputer {
+ return &PageRankWorker{}
+}
+
+func (prm *PageRankMaker) CreateMasterComputer() compute.MasterComputer {
+ return &PageRankMaster{}
+}
+
+func (prm *PageRankMaker) Name() string {
+ return "pagerank"
+}
+
+func (prm *PageRankMaker) DataNeeded() []string {
+ return []string{compute.UseOutDegree}
+}
+
+type PageRankWorker struct {
+ compute.WorkerComputerBase
+ damping serialize.SFloat32
+ initialRank serialize.SFloat32
+ danglingSumWorker serialize.SFloat32
+ danglingSum serialize.SFloat32
+ preDangling serialize.SFloat32
+ diffSum []serialize.SFloat32
+ newValues []serialize.SFloat32
+ oldValues []serialize.SFloat32
+ useProperty bool
+ edgeProperty string
+ propertyType structure.ValueType
+}
+
+func (pgw *PageRankWorker) VertexValue(i uint32) serialize.MarshalAble {
+ return &pgw.newValues[i]
+}
+
+//func (pgw *PageRankWorker) Scatters() []int {
+// return []int{len(pgw.WContext.GraphData.TotalVertex)}
+//}
+//
+//func (pgw *PageRankWorker) ScatterValue(sIdx int, vIdx int) serialize.MarshalAble {
+// _ = sIdx
+// return &pgw.newValues[vIdx]
+//}
+
+func (pgw *PageRankWorker) Init() error {
+ pgw.newValues = make([]serialize.SFloat32, pgw.WContext.GraphData.Vertex.TotalVertexCount())
+ pgw.oldValues = make([]serialize.SFloat32, pgw.WContext.GraphData.Vertex.TotalVertexCount())
+ pgw.diffSum = make([]serialize.SFloat32, pgw.WContext.Parallel)
+ initValue := 1.0 / serialize.SFloat32(pgw.WContext.GraphData.Vertex.TotalVertexCount())
+ for i := range pgw.oldValues {
+ pgw.oldValues[i] = initValue
+ }
+ pgw.damping = serialize.SFloat32(
+ options.GetFloat(pgw.WContext.Params, "pagerank.damping"))
+ pgw.initialRank = (1.0 - pgw.damping) / serialize.SFloat32(pgw.WContext.GraphData.Vertex.TotalVertexCount())
+ pgw.preDangling = pgw.damping / serialize.SFloat32(pgw.WContext.GraphData.Vertex.TotalVertexCount())
+ pgw.WContext.CreateValue("dangling_sum", compute.ValueTypeFloat32, compute.CValueActionAggregate)
+ pgw.WContext.SetValue("dangling_sum", serialize.SFloat32(0))
+ pgw.WContext.CreateValue("diff_sum", compute.ValueTypeFloat32, compute.CValueActionAggregate)
+ pgw.WContext.SetValue("diff_sum", serialize.SFloat32(0))
+ pgw.edgeProperty = options.GetString(pgw.WContext.Params, "pagerank.edge_weight_property")
+ if len(pgw.edgeProperty) > 0 {
+ vType, ok := pgw.WContext.GraphData.InEdgesProperty.GetValueType(pgw.edgeProperty)
+ if !ok {
+ logrus.Errorf("unknown edge weighted property:%v", pgw.edgeProperty)
+ return fmt.Errorf("unknown edge weighted property:%v", pgw.edgeProperty)
+ }
+ switch vType {
+ case structure.ValueTypeInt32, structure.ValueTypeFloat32:
+ pgw.propertyType = vType
+ case structure.ValueTypeString:
+ logrus.Errorf("illegal edge weighted property type:%v", pgw.edgeProperty)
+ return fmt.Errorf("illegal edge weighted property type:%v", pgw.edgeProperty)
+ }
+ pgw.useProperty = true
+ }
+ return nil
+}
+
+func (pgw *PageRankWorker) BeforeStep() {
+ for i := range pgw.diffSum {
+ pgw.diffSum[i] = 0
+ }
+ pgw.danglingSum = 0
+ for i := uint32(0); i < pgw.WContext.GraphData.Vertex.TotalVertexCount(); i++ {
+ if pgw.WContext.GraphData.Edges.GetOutDegree(i) == 0 {
+ pgw.danglingSum += pgw.oldValues[i]
+ }
+ }
+}
+
+func (pgw *PageRankWorker) Compute(vertexID uint32, pID int) {
+ newRank := serialize.SFloat32(0.0)
+ vertIdx := vertexID - pgw.WContext.GraphData.VertIDStart
+ inEdges := pgw.WContext.GraphData.Edges.GetInEdges(vertIdx)
+ if !pgw.useProperty {
+ for _, nID := range inEdges {
+ out := pgw.WContext.GraphData.Edges.GetOutDegree(uint32(nID))
+ newRank += pgw.oldValues[nID] / serialize.SFloat32(out)
+ }
+ } else {
+ for _, nID := range inEdges {
+ out := pgw.WContext.GraphData.Edges.GetOutDegree(uint32(nID))
+ edgeRank := pgw.oldValues[nID] / serialize.SFloat32(out)
+ switch pgw.propertyType {
+ case structure.ValueTypeInt32:
+ edgeRank *= serialize.SFloat32(pgw.WContext.GraphData.InEdgesProperty.GetInt32Value(
+ pgw.edgeProperty, vertIdx, uint32(nID)))
+ case structure.ValueTypeFloat32:
+ edgeRank *= pgw.WContext.GraphData.InEdgesProperty.GetFloat32Value(
+ pgw.edgeProperty, vertIdx, uint32(nID))
+ }
+ newRank += edgeRank
+ }
+ }
+ newRank = pgw.initialRank + pgw.damping*newRank + pgw.preDangling*pgw.danglingSum
+ pgw.diffSum[pID] += serialize.SFloat32(math.Abs(float64(newRank - pgw.newValues[vertexID])))
+ pgw.newValues[vertexID] = newRank
+}
+
+func (pgw *PageRankWorker) AfterStep() {
+ pgw.WContext.SetValue("dangling_sum", pgw.danglingSumWorker)
+ //endIdx := pgw.WContext.GraphData.VertIDStart + pgw.WContext.GraphData.VertexCount
+ diffSum := serialize.SFloat32(0.0)
+ for _, v := range pgw.diffSum {
+ diffSum += v
+ }
+ //for i := pgw.WContext.GraphData.VertIDStart; i < endIdx; i++ {
+ // diffSum += serialize.SFloat32(math.Abs(float64(pgw.oldValues[i] - pgw.newValues[i])))
+ //}
+ pgw.WContext.SetValue("diff_sum", diffSum)
+ copy(pgw.oldValues, pgw.newValues)
+}
+
+func (pgw *PageRankWorker) OutputValueType() string {
+ return common.HgValueTypeFloat
+}
+
+type PageRankMaster struct {
+ compute.MasterComputerBase
+ diffThreshold serialize.SFloat32
+}
+
+func (pgm *PageRankMaster) Init() error {
+ pgm.diffThreshold = serialize.SFloat32(options.GetFloat(pgm.MContext.Params, "pagerank.diff_threshold"))
+ return nil
+}
+
+func (pgm *PageRankMaster) Compute() bool {
+ diffSum := pgm.MContext.GetValue("diff_sum").(serialize.SFloat32)
+ logrus.Infof("different sum: %f, threshold: %f", diffSum, pgm.diffThreshold)
+ return pgm.MContext.GetValue("diff_sum").(serialize.SFloat32) >= pgm.diffThreshold
+}
diff --git a/vermeer/algorithms/personalized_pagerank.go b/vermeer/algorithms/personalized_pagerank.go
new file mode 100644
index 00000000..8cb2ba4d
--- /dev/null
+++ b/vermeer/algorithms/personalized_pagerank.go
@@ -0,0 +1,154 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import (
+ "fmt"
+ "math"
+ "vermeer/apps/common"
+ "vermeer/apps/compute"
+ "vermeer/apps/options"
+ "vermeer/apps/serialize"
+
+ "github.com/sirupsen/logrus"
+)
+
+func init() {
+ Algorithms = append(Algorithms, new(PersonalizedPagerankMaker))
+}
+
+type PersonalizedPagerankMaker struct {
+ compute.AlgorithmMakerBase
+}
+
+func (prm *PersonalizedPagerankMaker) CreateWorkerComputer() compute.WorkerComputer {
+ return &PersonalizedPagerankWorker{}
+}
+
+func (prm *PersonalizedPagerankMaker) CreateMasterComputer() compute.MasterComputer {
+ return &PersonalizedPagerankMaster{}
+}
+
+func (prm *PersonalizedPagerankMaker) Name() string {
+ return "ppr"
+}
+
+func (prm *PersonalizedPagerankMaker) DataNeeded() []string {
+ return []string{compute.UseOutDegree}
+}
+
+type PersonalizedPagerankWorker struct {
+ compute.WorkerComputerBase
+ source uint32
+ damping serialize.SFloat32
+ danglingSumWorker serialize.SFloat32
+ danglingSum serialize.SFloat32
+ diffSum []serialize.SFloat32
+ newValues []serialize.SFloat32
+ oldValues []serialize.SFloat32
+}
+
+func (pgw *PersonalizedPagerankWorker) VertexValue(i uint32) serialize.MarshalAble {
+ return &pgw.newValues[i]
+}
+
+func (pgw *PersonalizedPagerankWorker) Init() error {
+ pgw.newValues = make([]serialize.SFloat32, pgw.WContext.GraphData.Vertex.TotalVertexCount())
+ pgw.oldValues = make([]serialize.SFloat32, pgw.WContext.GraphData.Vertex.TotalVertexCount())
+ pgw.diffSum = make([]serialize.SFloat32, pgw.WContext.Parallel)
+ initValue := 1.0 / serialize.SFloat32(pgw.WContext.GraphData.Vertex.TotalVertexCount())
+ for i := range pgw.oldValues {
+ pgw.oldValues[i] = initValue
+ }
+ pgw.damping = serialize.SFloat32(
+ options.GetFloat(pgw.WContext.Params, "ppr.damping"))
+ logrus.Infof("damping:%v", pgw.damping)
+ var ok bool
+ pgw.source, ok = pgw.WContext.GraphData.Vertex.GetVertexIndex(options.GetString(pgw.WContext.Params, "ppr.source"))
+ if !ok {
+ logrus.Errorf("ppr.source:%v not exist", options.GetString(pgw.WContext.Params, "ppr.source"))
+ return fmt.Errorf("ppr.source:%v not exist", options.GetString(pgw.WContext.Params, "ppr.source"))
+ }
+ pgw.WContext.CreateValue("dangling_sum", compute.ValueTypeFloat32, compute.CValueActionAggregate)
+ pgw.WContext.SetValue("dangling_sum", serialize.SFloat32(0))
+ pgw.WContext.CreateValue("diff_sum", compute.ValueTypeFloat32, compute.CValueActionAggregate)
+ pgw.WContext.SetValue("diff_sum", serialize.SFloat32(0))
+ return nil
+}
+
+func (pgw *PersonalizedPagerankWorker) BeforeStep() {
+ for i := range pgw.diffSum {
+ pgw.diffSum[i] = 0
+ }
+ pgw.danglingSum = 0
+ for i := uint32(0); i < pgw.WContext.GraphData.Vertex.TotalVertexCount(); i++ {
+ if pgw.WContext.GraphData.Edges.GetOutDegree(i) == 0 {
+ pgw.danglingSum += pgw.oldValues[i]
+ }
+ }
+}
+
+func (pgw *PersonalizedPagerankWorker) Compute(vertexID uint32, pID int) {
+ newRank := serialize.SFloat32(0.0)
+ vertIdx := vertexID - pgw.WContext.GraphData.VertIDStart
+ for _, nID := range pgw.WContext.GraphData.Edges.GetInEdges(vertIdx) {
+ out := pgw.WContext.GraphData.Edges.GetOutDegree(uint32(nID))
+ newRank += pgw.oldValues[nID] / serialize.SFloat32(out)
+ }
+ newRank = pgw.damping * newRank
+ if vertexID == pgw.source {
+ newRank += (1.0 - pgw.damping) + pgw.damping*pgw.danglingSum
+ }
+
+ pgw.diffSum[pID] += serialize.SFloat32(math.Abs(float64(newRank - pgw.newValues[vertexID])))
+ pgw.newValues[vertexID] = newRank
+}
+
+func (pgw *PersonalizedPagerankWorker) AfterStep() {
+ pgw.WContext.SetValue("dangling_sum", pgw.danglingSumWorker)
+ //endIdx := pgw.WContext.GraphData.VertIDStart + pgw.WContext.GraphData.VertexCount
+ diffSum := serialize.SFloat32(0.0)
+ for _, v := range pgw.diffSum {
+ diffSum += v
+ }
+ //for i := pgw.WContext.GraphData.VertIDStart; i < endIdx; i++ {
+ // diffSum += serialize.SFloat32(math.Abs(float64(pgw.oldValues[i] - pgw.newValues[i])))
+ //}
+ pgw.WContext.SetValue("diff_sum", diffSum)
+ copy(pgw.oldValues, pgw.newValues)
+}
+
+func (pgw *PersonalizedPagerankWorker) OutputValueType() string {
+ return common.HgValueTypeFloat
+}
+
+type PersonalizedPagerankMaster struct {
+ compute.MasterComputerBase
+ diffThreshold serialize.SFloat32
+}
+
+func (pgm *PersonalizedPagerankMaster) Init() error {
+ pgm.diffThreshold = serialize.SFloat32(options.GetFloat(pgm.MContext.Params, "ppr.diff_threshold"))
+ return nil
+}
+
+func (pgm *PersonalizedPagerankMaster) Compute() bool {
+ diffSum := pgm.MContext.GetValue("diff_sum").(serialize.SFloat32)
+ logrus.Infof("different sum: %f, threshold: %f", diffSum, pgm.diffThreshold)
+ return pgm.MContext.GetValue("diff_sum").(serialize.SFloat32) >= pgm.diffThreshold
+}
diff --git a/vermeer/algorithms/scc.go b/vermeer/algorithms/scc.go
new file mode 100644
index 00000000..31f361e6
--- /dev/null
+++ b/vermeer/algorithms/scc.go
@@ -0,0 +1,248 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import (
+ "vermeer/apps/common"
+ "vermeer/apps/compute"
+ "vermeer/apps/serialize"
+
+ "github.com/sirupsen/logrus"
+)
+
+func init() {
+ Algorithms = append(Algorithms, new(SccMaker))
+}
+
+type SccMaker struct {
+ compute.AlgorithmMakerBase
+}
+
+func (sm *SccMaker) CreateWorkerComputer() compute.WorkerComputer {
+ return &SccWorker{}
+}
+
+func (sm *SccMaker) CreateMasterComputer() compute.MasterComputer {
+ return &SccMaster{}
+}
+
+func (sm *SccMaker) Name() string {
+ return "scc"
+}
+
+func (sm *SccMaker) DataNeeded() []string {
+ return []string{compute.UseOutEdge}
+}
+func (sm *SccMaker) SupportedStatistics() map[compute.StatisticsType]struct{} {
+ return map[compute.StatisticsType]struct{}{compute.StatisticsTypeCount: {}, compute.StatisticsTypeModularity: {}}
+}
+
+type sccStepType int
+
+const (
+ stepColoring sccStepType = iota
+ stepBackward
+)
+
+type SccWorker struct {
+ compute.WorkerComputerBase
+ sccStep sccStepType
+ isTrimmed []serialize.SUint8
+ backwardID []serialize.SUint8
+ colorID []serialize.SUint32
+ sccID []serialize.SUint32
+ diffSum []serialize.SInt32
+}
+
+func (scw *SccWorker) VertexValue(i uint32) serialize.MarshalAble {
+ if scw.WContext.Output {
+ return &scw.sccID[i]
+ }
+ switch scw.sccStep {
+ case stepColoring:
+ return &scw.colorID[i]
+ case stepBackward:
+ return &scw.backwardID[i]
+ }
+ return &scw.sccID[i]
+}
+
+func (scw *SccWorker) Init() error {
+ scw.sccStep = stepColoring
+ scw.isTrimmed = make([]serialize.SUint8, scw.WContext.GraphData.Vertex.TotalVertexCount())
+ scw.sccID = make([]serialize.SUint32, scw.WContext.GraphData.Vertex.TotalVertexCount())
+ for v := range scw.sccID {
+ scw.sccID[v] = serialize.SUint32(v)
+ }
+ scw.colorID = make([]serialize.SUint32, scw.WContext.GraphData.Vertex.TotalVertexCount())
+ for v := range scw.colorID {
+ scw.colorID[v] = serialize.SUint32(v)
+ }
+ scw.diffSum = make([]serialize.SInt32, scw.WContext.Parallel)
+ scw.WContext.CreateValue("step", compute.ValueTypeInt32, compute.CValueActionAggregate)
+ scw.WContext.SetValue("step", serialize.SInt32(stepColoring))
+ scw.WContext.CreateValue("diff_sum", compute.ValueTypeInt32, compute.CValueActionAggregate)
+ scw.WContext.SetValue("diff_sum", serialize.SInt32(0))
+ scw.WContext.CreateValue("active_count", compute.ValueTypeInt32, compute.CValueActionAggregate)
+ scw.WContext.SetValue("active_count", serialize.SInt32(0))
+ return nil
+}
+
+func (scw *SccWorker) BeforeStep() {
+ oldSccStep := scw.sccStep
+ scw.sccStep = sccStepType(scw.WContext.GetValue("step").(serialize.SInt32))
+ if oldSccStep != scw.sccStep {
+ switch oldSccStep {
+ case stepColoring:
+ scw.backwardID = make([]serialize.SUint8, scw.WContext.GraphData.Vertex.TotalVertexCount())
+ for v, color := range scw.colorID {
+ if scw.isTrimmed[v] == 1 {
+ continue
+ }
+ if serialize.SUint32(v) == color {
+ scw.backwardID[v] = 1
+ }
+ }
+ case stepBackward:
+ for v, bw := range scw.backwardID {
+ if scw.isTrimmed[v] == 1 {
+ continue
+ }
+ if bw == 1 {
+ scw.sccID[v] = scw.colorID[v]
+ scw.isTrimmed[v] = 1
+ }
+ }
+ scw.colorID = make([]serialize.SUint32, scw.WContext.GraphData.Vertex.TotalVertexCount())
+ for v := range scw.colorID {
+ scw.colorID[v] = serialize.SUint32(v)
+ }
+ }
+ }
+ for i := range scw.diffSum {
+ scw.diffSum[i] = 0
+ }
+}
+
+func (scw *SccWorker) Compute(vertexID uint32, pID int) {
+ vertID := vertexID - scw.WContext.GraphData.VertIDStart
+ if scw.isTrimmed[vertexID] == 1 {
+ return
+ }
+ switch scw.sccStep {
+ case stepColoring:
+ oldValue := scw.colorID[vertexID]
+ for _, nID := range scw.WContext.GraphData.Edges.GetOutEdges(vertID) {
+ if scw.isTrimmed[nID] == 1 {
+ continue
+ }
+ if scw.colorID[vertexID] > scw.colorID[nID] {
+ scw.colorID[vertexID] = scw.colorID[nID]
+ }
+ }
+ if scw.colorID[vertexID] != oldValue {
+ scw.diffSum[pID]++
+ }
+ case stepBackward:
+ if scw.backwardID[vertexID] == 1 {
+ return
+ }
+ colorID := scw.colorID[vertexID]
+ for _, nID := range scw.WContext.GraphData.Edges.GetInEdges(vertID) {
+ if scw.isTrimmed[nID] == 1 || scw.colorID[nID] != colorID {
+ continue
+ }
+ if scw.backwardID[nID] == 1 {
+ scw.backwardID[vertexID] = 1
+ scw.diffSum[pID]++
+ break
+ }
+ }
+ }
+}
+
+func (scw *SccWorker) AfterStep() {
+ var count serialize.SInt32
+ for _, c := range scw.diffSum {
+ count += c
+ }
+ scw.WContext.SetValue("diff_sum", count)
+ activeCount := 0
+ for vID := scw.WContext.GraphData.VertIDStart; vID <
+ scw.WContext.GraphData.VertIDStart+scw.WContext.GraphData.VertexCount; vID++ {
+ if scw.isTrimmed[vID] == 0 {
+ activeCount++
+ }
+ }
+ scw.WContext.SetValue("active_count", serialize.SInt32(activeCount))
+ //scc := make(map[serialize.SUint32]int)
+ //for _, value := range scw.sccID {
+ // scc[value]++
+ //}
+ //max := 0
+ //for _, count := range scc {
+ // if count > max {
+ // max = count
+ // }
+ //}
+ //logrus.Infof("scc len:%v, max scc:%v", len(scc), max)
+}
+
+func (scw *SccWorker) OutputValueType() string {
+ return common.HgValueTypeInt
+}
+
+type SccMaster struct {
+ compute.MasterComputerBase
+ sccStep sccStepType
+}
+
+func (scm *SccMaster) Init() error {
+ scm.sccStep = stepColoring
+ return nil
+}
+
+func (scm *SccMaster) Compute() bool {
+ activeCount := scm.MContext.GetValue("active_count").(serialize.SInt32)
+ logrus.Infof("active count:%v", activeCount)
+ if activeCount == 0 {
+ return false
+ }
+ switch scm.sccStep {
+ case stepColoring:
+ diffSum := scm.MContext.GetValue("diff_sum").(serialize.SInt32)
+ logrus.Infof("coloring diff sum: %v", diffSum)
+ if diffSum == 0 {
+ scm.MContext.SetValue("step", serialize.SInt32(stepBackward))
+ scm.sccStep = stepBackward
+ } else {
+ scm.MContext.SetValue("step", serialize.SInt32(stepColoring))
+ }
+ case stepBackward:
+ diffSum := scm.MContext.GetValue("diff_sum").(serialize.SInt32)
+ logrus.Infof("backward diff sum: %v", diffSum)
+ if diffSum == 0 {
+ scm.MContext.SetValue("step", serialize.SInt32(stepColoring))
+ scm.sccStep = stepColoring
+ } else {
+ scm.MContext.SetValue("step", serialize.SInt32(stepBackward))
+ }
+ }
+
+ return true
+}
diff --git a/vermeer/algorithms/slpa.go b/vermeer/algorithms/slpa.go
new file mode 100644
index 00000000..f34c9929
--- /dev/null
+++ b/vermeer/algorithms/slpa.go
@@ -0,0 +1,251 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import (
+ "container/heap"
+ "vermeer/apps/common"
+ "vermeer/apps/compute"
+ "vermeer/apps/options"
+ "vermeer/apps/serialize"
+
+ "github.com/bytedance/gopkg/lang/fastrand"
+ "github.com/sirupsen/logrus"
+)
+
+func init() {
+ Algorithms = append(Algorithms, new(SlpaMaker))
+}
+
+type SlpaMaker struct {
+ compute.AlgorithmMakerBase
+}
+
+func (lm *SlpaMaker) CreateWorkerComputer() compute.WorkerComputer {
+ return &SlpaWorker{}
+}
+
+func (lm *SlpaMaker) CreateMasterComputer() compute.MasterComputer {
+ return &SlpaMaster{}
+}
+
+func (lm *SlpaMaker) Name() string {
+ return "slpa"
+}
+
+func (lm *SlpaMaker) DataNeeded() []string {
+ return []string{compute.UseOutEdge}
+}
+func (lm *SlpaMaker) SupportedStatistics() map[compute.StatisticsType]struct{} {
+ return map[compute.StatisticsType]struct{}{compute.StatisticsTypeCount: {}}
+}
+
+type SlpaWorker struct {
+ compute.WorkerComputerBase
+ // 全部prevlabels,供计算时读取,在afterstep中更新
+ prevlabels []serialize.MapUint32Float32
+ // 仅当前worker的newlabels,当前轮计算出的新newlabels
+ newlabels []serialize.MapUint32Float32
+ // 总轮次数
+ maxStep int
+ // 最多保留的标签数量
+ k int
+ // 选举标签的方法
+ selectMethod int
+}
+
+const (
+ selectMethodMax = iota
+ selectMethodRand
+)
+
+func (sw *SlpaWorker) VertexValue(i uint32) serialize.MarshalAble {
+ return &sw.newlabels[i]
+}
+
+func (sw *SlpaWorker) Init() error {
+ // 初始化label为自增id
+ sw.prevlabels = make([]serialize.MapUint32Float32, sw.WContext.GraphData.Vertex.TotalVertexCount())
+ sw.newlabels = make([]serialize.MapUint32Float32, sw.WContext.GraphData.Vertex.TotalVertexCount())
+
+ sw.maxStep = options.GetInt(sw.WContext.Params, "compute.max_step")
+ if sw.maxStep > 1000 {
+ logrus.Infof("max_step:%v is too large, set to 1000", sw.maxStep)
+ sw.maxStep = 1000
+ }
+ sw.k = options.GetInt(sw.WContext.Params, "slpa.k")
+
+ selectMethod := options.GetString(sw.WContext.Params, "slpa.select_method")
+ switch selectMethod {
+ case "max":
+ sw.selectMethod = selectMethodMax
+ case "random":
+ sw.selectMethod = selectMethodRand
+ default:
+ logrus.Errorf("slpa.select_method:%v is not supported, use default", selectMethod)
+ }
+
+ // init labels
+ for i := uint32(0); i < sw.WContext.GraphData.Vertex.TotalVertexCount(); i++ {
+ sw.prevlabels[i] = make(serialize.MapUint32Float32)
+ sw.prevlabels[i][serialize.SUint32(i)] = 1
+ sw.newlabels[i] = make(serialize.MapUint32Float32)
+ sw.newlabels[i][serialize.SUint32(i)] = 1
+ }
+
+ return nil
+}
+
+func (sw *SlpaWorker) BeforeStep() {
+ if sw.WContext.Step == int32(sw.maxStep) {
+ logrus.Infof("step:%v, finish slpa, post processing", sw.WContext.Step)
+ }
+}
+
+func (sw *SlpaWorker) selectNeighborLabel(listener uint32, speaker uint32) uint32 {
+ switch sw.selectMethod {
+ case selectMethodMax:
+ return sw.selectNeighborLabelMax(listener, speaker)
+ case selectMethodRand:
+ return sw.selectNeighborLabelRand(listener, speaker)
+ default:
+ logrus.Errorf("slpa.select_method:%v is not supported", sw.selectMethod)
+ }
+ return 0
+}
+
+func (sw *SlpaWorker) selectNeighborLabelMax(lisenser uint32, speaker uint32) uint32 {
+ maxWeight := serialize.SFloat32(0)
+ maxLabel := 0
+ for label, weight := range sw.prevlabels[speaker] {
+ if weight > maxWeight {
+ maxWeight = weight
+ maxLabel = int(label)
+ }
+ }
+ return uint32(maxLabel)
+}
+
+func (sw *SlpaWorker) selectNeighborLabelRand(listener uint32, speaker uint32) uint32 {
+ var sum float64
+ for _, weight := range sw.prevlabels[speaker] {
+ sum += float64(weight)
+ }
+
+ randNum := fastrand.Float64()
+
+ var current float64
+ for label, weight := range sw.prevlabels[speaker] {
+ next := current + float64(weight)
+ if randNum >= (current/sum) && randNum < (next/sum) {
+ return uint32(label)
+ }
+ current = next
+
+ }
+
+ logrus.Errorf("error in selectNeighborLabel, listener=%v, speaker=%v,labels[speaker]=%v, ", listener, speaker, sw.prevlabels[speaker])
+ return 0
+}
+
+func (sw *SlpaWorker) Compute(vertexID uint32, pID int) {
+ _ = pID
+ vertexIdx := vertexID - sw.WContext.GraphData.VertIDStart
+
+ if sw.WContext.Step < int32(sw.maxStep) {
+ // count key:自增label value:frequency
+ count := make(map[uint32]float64)
+ for _, inNode := range sw.WContext.GraphData.Edges.GetInEdges(vertexIdx) {
+ count[sw.selectNeighborLabel(vertexID, uint32(inNode))]++
+ }
+ for _, outNode := range sw.WContext.GraphData.Edges.GetOutEdges(vertexIdx) {
+ count[sw.selectNeighborLabel(vertexID, uint32(outNode))]++
+ }
+
+ // 得到maxCount
+ maxCount := float64(0)
+ for _, labelCount := range count {
+ if maxCount < labelCount {
+ maxCount = labelCount
+ }
+ }
+
+ // 得到满足maxCount的所有自增label
+ candidate := make([]uint32, 0)
+ for originLabel, labelCount := range count {
+ if labelCount == maxCount {
+ candidate = append(candidate, originLabel)
+ }
+ }
+ if len(candidate) == 0 {
+ return
+ }
+
+ // 比较originLabel
+ minLabel := candidate[0]
+ for _, candiLabel := range candidate {
+ originLabel := candiLabel
+ if originLabel < minLabel {
+ minLabel = originLabel
+ }
+ }
+ sw.newlabels[vertexID][serialize.SUint32(minLabel)]++
+ } else {
+ // post-processing, select final labels
+ if sw.k < 0 || sw.k >= len(sw.newlabels[vertexID]) {
+ return
+ }
+
+ labelFreqs := make(common.VertexWeights, 0, sw.k)
+
+ heap.Init(&labelFreqs)
+
+ for label, freq := range sw.newlabels[vertexID] {
+ heap.Push(&labelFreqs, common.VertexWeight{Vertex: uint32(label), Weight: float64(freq)})
+ if labelFreqs.Len() > sw.k {
+ heap.Pop(&labelFreqs)
+ }
+ }
+
+ sw.newlabels[vertexID] = make(serialize.MapUint32Float32, sw.k)
+ for _, labelFreq := range labelFreqs {
+ sw.newlabels[vertexID][serialize.SUint32(labelFreq.Vertex)] = serialize.SFloat32(labelFreq.Weight)
+ }
+ }
+
+}
+
+func (sw *SlpaWorker) AfterStep() {
+ for vertexID, LabelFreq := range sw.newlabels {
+ for label, freq := range LabelFreq {
+ sw.prevlabels[vertexID][label] = freq
+ }
+ }
+}
+
+func (sw *SlpaWorker) OutputValueType() string {
+ return common.HgValueTypeString
+}
+
+type SlpaMaster struct {
+ compute.MasterComputerBase
+}
+
+func (sm *SlpaMaster) Compute() bool {
+ return true
+}
diff --git a/vermeer/algorithms/sssp.go b/vermeer/algorithms/sssp.go
new file mode 100644
index 00000000..5171f628
--- /dev/null
+++ b/vermeer/algorithms/sssp.go
@@ -0,0 +1,125 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import (
+ "fmt"
+ "vermeer/apps/common"
+ "vermeer/apps/compute"
+ "vermeer/apps/options"
+ "vermeer/apps/serialize"
+
+ "github.com/sirupsen/logrus"
+)
+
+func init() {
+ Algorithms = append(Algorithms, new(SsspMaker))
+}
+
+type SsspMaker struct {
+ compute.AlgorithmMakerBase
+}
+
+func (sp *SsspMaker) CreateWorkerComputer() compute.WorkerComputer {
+ return &SsspWorker{}
+}
+
+func (sp *SsspMaker) CreateMasterComputer() compute.MasterComputer {
+ return &SsspMaster{}
+}
+
+func (sp *SsspMaker) Name() string {
+ return "sssp"
+}
+
+func (sp *SsspMaker) DataNeeded() []string {
+ return []string{}
+}
+
+type SsspWorker struct {
+ compute.WorkerComputerBase
+ srcID serialize.SUint32
+ newValues []serialize.SInt32
+ isContinue serialize.SInt32
+ srcExist serialize.SInt32
+}
+
+func (sw *SsspWorker) VertexValue(i uint32) serialize.MarshalAble {
+ return &sw.newValues[i]
+}
+
+func (sw *SsspWorker) Init() error {
+ sw.newValues = make([]serialize.SInt32, sw.WContext.GraphData.Vertex.TotalVertexCount())
+ // 不可达节点的最短路径为-1
+ for i := uint32(0); i < sw.WContext.GraphData.Vertex.TotalVertexCount(); i++ {
+ sw.newValues[i] = -1
+ }
+
+ originID := options.GetString(sw.WContext.Params, "sssp.source")
+ srcID, ok := sw.WContext.GraphData.Vertex.GetVertexIndex(originID)
+ if !ok {
+ sw.srcExist = 0
+ logrus.Errorf("no node id named:%v", originID)
+ return fmt.Errorf(`unknown source node id named:%v`, originID)
+ } else {
+ sw.srcExist = 1
+ sw.srcID = serialize.SUint32(srcID)
+ logrus.Infof("sssp init originID: %s, shortId: %d", originID, sw.srcID)
+ sw.newValues[sw.srcID] = 0
+ }
+ sw.WContext.CreateValue("is_continue", compute.ValueTypeInt32, compute.CValueActionAggregate)
+ return nil
+}
+
+func (sw *SsspWorker) BeforeStep() {
+ sw.isContinue = 0
+}
+
+func (sw *SsspWorker) Compute(vertexId uint32, pId int) {
+ _ = pId
+ if sw.srcExist == 0 || sw.newValues[vertexId] != -1 {
+ return
+ }
+ vertexIdx := vertexId - sw.WContext.GraphData.VertIDStart
+ for _, nid := range sw.WContext.GraphData.Edges.GetInEdges(vertexIdx) {
+ if sw.newValues[nid] == serialize.SInt32(sw.WContext.Step-1) {
+ sw.newValues[vertexId] = serialize.SInt32(sw.WContext.Step)
+ sw.isContinue = 1
+ return
+ }
+ }
+}
+
+func (sw *SsspWorker) OutputValueType() string {
+ return common.HgValueTypeFloat
+}
+
+func (sw *SsspWorker) AfterStep() {
+ sw.WContext.SetValue("is_continue", sw.isContinue)
+
+}
+
+type SsspMaster struct {
+ compute.MasterComputerBase
+}
+
+func (sm *SsspMaster) Compute() bool {
+ //is_continue >1 表示继续迭代;=0 表示终止
+ isContinue := sm.MContext.GetValue("is_continue").(serialize.SInt32)
+ return isContinue != 0
+}
diff --git a/vermeer/algorithms/triangle_count.go b/vermeer/algorithms/triangle_count.go
new file mode 100644
index 00000000..3dcc91f6
--- /dev/null
+++ b/vermeer/algorithms/triangle_count.go
@@ -0,0 +1,150 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import (
+ "vermeer/apps/common"
+ "vermeer/apps/compute"
+ "vermeer/apps/serialize"
+)
+
+func init() {
+ Algorithms = append(Algorithms, new(TriangleCountMaker))
+}
+
+type TriangleCountMaker struct {
+ compute.AlgorithmMakerBase
+}
+
+func (tc *TriangleCountMaker) CreateWorkerComputer() compute.WorkerComputer {
+ return &TriangleCountWorker{}
+}
+
+func (tc *TriangleCountMaker) CreateMasterComputer() compute.MasterComputer {
+ return &TriangleCountMaster{}
+}
+
+func (tc *TriangleCountMaker) Name() string {
+ return "triangle_count"
+}
+
+func (tc *TriangleCountMaker) DataNeeded() []string {
+ return []string{compute.UseOutEdge}
+}
+
+type TriangleCountWorker struct {
+ compute.WorkerComputerBase
+ triangleCount []serialize.SUint32
+ totalNeighborSet []serialize.MapUint32Struct
+ selfNeighborSet []map[serialize.SUint32]struct{}
+}
+
+func (tcw *TriangleCountWorker) VertexValue(i uint32) serialize.MarshalAble {
+ if tcw.WContext.Step == 1 {
+ return &tcw.totalNeighborSet[i]
+ }
+ return &tcw.triangleCount[i]
+}
+
+func (tcw *TriangleCountWorker) Init() error {
+ tcw.triangleCount = make([]serialize.SUint32, tcw.WContext.GraphData.Vertex.TotalVertexCount())
+ tcw.totalNeighborSet = make([]serialize.MapUint32Struct, tcw.WContext.GraphData.Vertex.TotalVertexCount())
+ tcw.selfNeighborSet = make([]map[serialize.SUint32]struct{}, tcw.WContext.GraphData.VertexCount)
+ return nil
+}
+
+func (tcw *TriangleCountWorker) Compute(vertexID uint32, pID int) {
+ _ = pID
+ vertID := vertexID - tcw.WContext.GraphData.VertIDStart
+ switch tcw.WContext.Step {
+ case 1:
+ //if tcw.WContext.GraphData.BothEdges != nil {
+ // tcw.selfNeighborSet[vertID] = make(map[serialize.SUint32]struct{},
+ // len(tcw.WContext.GraphData.BothEdges[vertID]))
+ // tcw.totalNeighborSet[vertexID] = make(map[serialize.SUint32]struct{},
+ // len(tcw.WContext.GraphData.BothEdges[vertID])/2)
+ // for _, nID := range tcw.WContext.GraphData.BothEdges[vertID] {
+ // if uint32(nID) == vertexID {
+ // continue
+ // }
+ // tcw.selfNeighborSet[vertID][nID] = struct{}{}
+ // if uint32(nID) < vertexID {
+ // tcw.totalNeighborSet[vertexID][nID] = struct{}{}
+ // }
+ // }
+ //} else {
+ inEdges := tcw.WContext.GraphData.Edges.GetInEdges(vertID)
+ outEdges := tcw.WContext.GraphData.Edges.GetOutEdges(vertID)
+ tcw.selfNeighborSet[vertID] = make(map[serialize.SUint32]struct{},
+ len(inEdges)+len(outEdges))
+ tcw.totalNeighborSet[vertexID] = make(map[serialize.SUint32]struct{},
+ (len(inEdges)+len(outEdges))/2)
+ for _, nID := range inEdges {
+ if uint32(nID) == vertexID {
+ continue
+ }
+ tcw.selfNeighborSet[vertID][nID] = struct{}{}
+ if uint32(nID) < vertexID {
+ tcw.totalNeighborSet[vertexID][nID] = struct{}{}
+ }
+ }
+ for _, nID := range outEdges {
+ if uint32(nID) == vertexID {
+ continue
+ }
+ tcw.selfNeighborSet[vertID][nID] = struct{}{}
+ if uint32(nID) < vertexID {
+ tcw.totalNeighborSet[vertexID][nID] = struct{}{}
+ }
+ }
+ //}
+ case 2:
+ for fID := range tcw.selfNeighborSet[vertID] {
+ tcw.triangleCount[vertexID] += tcw.findCross(tcw.selfNeighborSet[vertID], tcw.totalNeighborSet[fID])
+ }
+ }
+}
+
+func (tcw *TriangleCountWorker) findCross(a map[serialize.SUint32]struct{}, b map[serialize.SUint32]struct{}) serialize.SUint32 {
+ var count serialize.SUint32 = 0
+ if len(a) > len(b) {
+ for k := range b {
+ if _, ok := a[k]; ok {
+ count++
+ }
+ }
+ } else {
+ for k := range a {
+ if _, ok := b[k]; ok {
+ count++
+ }
+ }
+ }
+ return count
+}
+func (tcw *TriangleCountWorker) OutputValueType() string {
+ return common.HgValueTypeInt
+}
+
+type TriangleCountMaster struct {
+ compute.MasterComputerBase
+}
+
+func (tcm *TriangleCountMaster) Compute() bool {
+ return tcm.MContext.Step != 2
+}
diff --git a/vermeer/algorithms/wcc.go b/vermeer/algorithms/wcc.go
new file mode 100644
index 00000000..1efab3fc
--- /dev/null
+++ b/vermeer/algorithms/wcc.go
@@ -0,0 +1,129 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package algorithms
+
+import (
+ "vermeer/apps/common"
+ "vermeer/apps/compute"
+ "vermeer/apps/serialize"
+
+ "github.com/sirupsen/logrus"
+)
+
+func init() {
+ Algorithms = append(Algorithms, new(WccMaker))
+}
+
+type WccMaker struct {
+ compute.AlgorithmMakerBase
+}
+
+func (wm *WccMaker) CreateWorkerComputer() compute.WorkerComputer {
+ return &WccWorker{}
+}
+
+func (wm *WccMaker) CreateMasterComputer() compute.MasterComputer {
+ return &WccMaster{}
+}
+
+func (wm *WccMaker) Name() string {
+ return "wcc"
+}
+
+func (wm *WccMaker) DataNeeded() []string {
+ return []string{compute.UseOutEdge}
+}
+
+func (wm *WccMaker) SupportedStatistics() map[compute.StatisticsType]struct{} {
+ return map[compute.StatisticsType]struct{}{compute.StatisticsTypeCount: {}, compute.StatisticsTypeModularity: {}, compute.StatisticsTypeSketchCount: {}}
+}
+
+type WccWorker struct {
+ compute.WorkerComputerBase
+ connectedMinID []serialize.SUint32
+ diffSum []serialize.SInt32
+}
+
+func (wcw *WccWorker) VertexValue(i uint32) serialize.MarshalAble {
+ return &wcw.connectedMinID[i]
+}
+func (wcw *WccWorker) Init() error {
+ wcw.connectedMinID = make([]serialize.SUint32, wcw.WContext.GraphData.Vertex.TotalVertexCount())
+ for i := uint32(0); i < wcw.WContext.GraphData.Vertex.TotalVertexCount(); i++ {
+ wcw.connectedMinID[i] = serialize.SUint32(i)
+ }
+ wcw.WContext.CreateValue("diff_sum", compute.ValueTypeInt32, compute.CValueActionAggregate)
+ wcw.WContext.SetValue("diff_sum", serialize.SInt32(0))
+ wcw.diffSum = make([]serialize.SInt32, wcw.WContext.Parallel)
+ return nil
+}
+
+func (wcw *WccWorker) BeforeStep() {
+ for i := range wcw.diffSum {
+ wcw.diffSum[i] = 0
+ }
+}
+
+func (wcw *WccWorker) Compute(vertexID uint32, pID int) {
+ _ = pID
+ vertIDx := vertexID - wcw.WContext.GraphData.VertIDStart
+ oldValue := wcw.connectedMinID[vertexID]
+ //if wcw.WContext.GraphData.BothEdges != nil {
+ // for _, nID := range wcw.WContext.GraphData.BothEdges[vertIDx] {
+ // if wcw.connectedMinID[vertexID] > wcw.connectedMinID[nID] {
+ // wcw.connectedMinID[vertexID] = wcw.connectedMinID[nID]
+ // }
+ // }
+ //} else {
+ for _, nID := range wcw.WContext.GraphData.Edges.GetInEdges(vertIDx) {
+ if wcw.connectedMinID[vertexID] > wcw.connectedMinID[nID] {
+ wcw.connectedMinID[vertexID] = wcw.connectedMinID[nID]
+ }
+ }
+ for _, nID := range wcw.WContext.GraphData.Edges.GetOutEdges(vertIDx) {
+ if wcw.connectedMinID[vertexID] > wcw.connectedMinID[nID] {
+ wcw.connectedMinID[vertexID] = wcw.connectedMinID[nID]
+ }
+ }
+ //}
+ if oldValue != wcw.connectedMinID[vertexID] {
+ wcw.diffSum[pID]++
+ }
+}
+
+func (wcw *WccWorker) OutputValueType() string {
+ return common.HgValueTypeInt
+}
+
+func (wcw *WccWorker) AfterStep() {
+ var diffSum serialize.SInt32
+ for _, v := range wcw.diffSum {
+ diffSum += v
+ }
+ wcw.WContext.SetValue("diff_sum", diffSum)
+}
+
+type WccMaster struct {
+ compute.MasterComputerBase
+}
+
+func (wcm *WccMaster) Compute() bool {
+ diffSum := wcm.MContext.GetValue("diff_sum").(serialize.SInt32)
+ logrus.Infof("different sum: %v", diffSum)
+ return diffSum != 0
+}
diff --git a/vermeer/apps/auth/filter.go b/vermeer/apps/auth/filter.go
new file mode 100644
index 00000000..d87a34ea
--- /dev/null
+++ b/vermeer/apps/auth/filter.go
@@ -0,0 +1,184 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package auth
+
+import (
+ "encoding/json"
+ "net/http"
+ "strings"
+ "time"
+ "vermeer/apps/common"
+ "vermeer/apps/structure"
+
+ "github.com/gin-gonic/gin"
+ "github.com/mr-tron/base58"
+
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ HeaderKey = "Authorization"
+ CookieKey = "vat" // Vermeer Auth Token
+ tokenMaxAge = 24 * 60 * 60 // in seconds
+)
+
+var factory *TokenFactory
+
+func Init() {
+ factor := common.GetConfig("auth_token_factor").(string)
+ factory = &TokenFactory{Factor: factor}
+}
+
+func NoneAuthFilter(ctx *gin.Context) {
+ credential := structure.DefaultCredential()
+ logrus.Debug("default user : ", credential.User())
+ logrus.Debug("default space: ", credential.Space())
+ ctx.Set(structure.CredentialKey, structure.DefaultCredential())
+}
+
+func TokenFilter(ctx *gin.Context) {
+ var tokenStr string
+ var token *Token
+ var credential *structure.Credential
+ var err error
+ var text string
+ header := ctx.GetHeader(HeaderKey)
+ if header == "" {
+ if str, err := ctx.Cookie(CookieKey); err != nil {
+ goto LOGIN
+ } else {
+ tokenStr = DecodeBase58(str)
+ }
+ } else {
+ tokenStr = DecodeBase58(getTokenStr(header))
+ }
+
+ if tokenStr == "" {
+ goto LOGIN
+ }
+
+ token = ToToken(tokenStr)
+ if token == nil {
+ goto LOGIN
+ }
+
+ if !factory.Verify(token) {
+ text = "Invalid Token"
+ goto LOGIN
+ }
+
+ if time.Now().Unix()-token.Time > tokenMaxAge {
+ text = "Expired Token"
+ goto LOGIN
+ }
+
+ logrus.Debugf("token: %s@%s", token.User, token.Space)
+
+ //if structure.AdminName == token.User {
+ // credential = structure.AdminCredential()
+ //} else {
+ credential, err = structure.NewCredential(token.User, token.Space)
+ //}
+ if err != nil {
+ logrus.Error(err)
+ goto LOGIN
+ }
+ ctx.Set(structure.CredentialKey, credential)
+ return
+
+LOGIN:
+ login(ctx, text)
+}
+
+func AdminFilter(ctx *gin.Context) {
+ var cred *structure.Credential
+ if v, ok := ctx.Get(structure.CredentialKey); ok {
+ if c, ok := v.(*structure.Credential); ok {
+ cred = c
+ }
+ }
+
+ if cred == nil {
+ login(ctx, "")
+ return
+ }
+
+ if !cred.IsAdmin() {
+ login(ctx, "admin permission is required")
+ return
+ }
+
+}
+
+func login(ctx *gin.Context, text string) {
+ if text == "" {
+ text = http.StatusText(http.StatusUnauthorized)
+ }
+ ctx.JSON(http.StatusUnauthorized, gin.H{
+ "errcode": -1,
+ "message": text,
+ })
+ ctx.Abort()
+}
+
+// compatible for hugegraph header: "Bearer XXXXXX"
+func getTokenStr(header string) (tokenStr string) {
+ if header == "" {
+ return ""
+ }
+ if strings.HasPrefix(header, "Bearer ") {
+ tokenStr = header[7:]
+ } else {
+ tokenStr = header
+ }
+ return
+}
+
+func ToTokenBase58(tokenBase58 string) *Token {
+ return ToToken(DecodeBase58(tokenBase58))
+}
+
+func ToToken(tokenStr string) *Token {
+ var token = new(Token)
+ if err := json.Unmarshal([]byte(tokenStr), token); err == nil {
+ return token
+ }
+ return nil
+}
+
+func ToBase58(str string) string {
+ if str == "" {
+ return ""
+ }
+ return base58.Encode([]byte(str))
+}
+
+func DecodeBase58(str string) string {
+ if str == "" {
+ return ""
+ }
+
+ var b []byte
+ var err error
+ if b, err = base58.Decode(str); err != nil {
+ return ""
+ }
+ res := string(b)
+ return res
+
+}
diff --git a/vermeer/apps/auth/token.go b/vermeer/apps/auth/token.go
new file mode 100644
index 00000000..725c46df
--- /dev/null
+++ b/vermeer/apps/auth/token.go
@@ -0,0 +1,119 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package auth
+
+import (
+ "crypto/sha1"
+ "encoding/hex"
+ "io"
+ "strconv"
+ "strings"
+ "time"
+)
+
+var empty = ""
+
+const defaultFactor = "!^_^!YouCanTypeWhateverYouWant"
+
+type Token struct {
+ User string `json:"u"` // Login ID
+ Space string `json:"s"` // Graph space
+ Nick string `json:"n"` // [Nickname]
+ Digest string `json:"d"` // Digest
+ Client string `json:"c"` // Identifier of invoker, e.g. IP address, SN of device.
+ Time int64 `json:"t"` // Note: time in seconds form not milliseconds
+}
+
+type TokenFactory struct {
+ Factor string
+ randoms *[4]string
+}
+
+func (factory *TokenFactory) getRandoms() *[4]string {
+ if factory.randoms == nil {
+ if factory.Factor == "" {
+ factory.Factor = defaultFactor
+ }
+
+ if len(factory.Factor) < 4 {
+ factory.Factor = factory.Factor + defaultFactor
+ }
+
+ buf := []rune(factory.Factor)
+ var r [4]string
+ l := len(buf) / 4
+
+ for i := 0; i < 4; i++ {
+ r[i] = string(buf[i*l : (i+1)*l])
+ }
+
+ factory.randoms = &r
+ }
+ return factory.randoms
+}
+
+// NewToken Create a Token by login-id, space, client(e.g. IP address).
+func (factory *TokenFactory) NewToken(user string, space string, client string) *Token {
+ if user == "" {
+ return nil
+ }
+ if client == "" {
+ client = empty
+ }
+ return &Token{User: user, Space: space, Client: client, Time: time.Now().Unix()}
+}
+
+func (factory *TokenFactory) Sign(a *Token) {
+ if a == nil {
+ return
+ }
+ a.Digest = digest(factory.signSource(a))
+}
+
+func (factory *TokenFactory) Verify(a *Token) bool {
+ if a == nil {
+ return false
+ }
+
+ s := digest(factory.signSource(a))
+
+ return a.Digest == s
+}
+
+func digest(s string) string {
+ if s == "" {
+ return ""
+ }
+ h := sha1.New()
+ _, _ = io.WriteString(h, s)
+ return hex.EncodeToString(h.Sum(nil))
+}
+
+func (factory *TokenFactory) signSource(a *Token) string {
+ var sb strings.Builder
+ sb.WriteString(factory.getRandoms()[0])
+ sb.WriteString(a.User)
+ sb.WriteString(factory.getRandoms()[1])
+ sb.WriteString(a.Space)
+ sb.WriteString(factory.getRandoms()[2])
+ sb.WriteString(strconv.FormatInt(a.Time, 10))
+ sb.WriteString(factory.getRandoms()[3])
+ sb.WriteString(a.Client)
+ res := sb.String()
+ return res
+}
diff --git a/vermeer/apps/bsp/bsp.go b/vermeer/apps/bsp/bsp.go
new file mode 100644
index 00000000..a6eaa87c
--- /dev/null
+++ b/vermeer/apps/bsp/bsp.go
@@ -0,0 +1,18 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package bsp
diff --git a/vermeer/apps/bsp/bsp_consts.go b/vermeer/apps/bsp/bsp_consts.go
new file mode 100644
index 00000000..a6eaa87c
--- /dev/null
+++ b/vermeer/apps/bsp/bsp_consts.go
@@ -0,0 +1,18 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package bsp
diff --git a/vermeer/apps/buffer/double_buffer.go b/vermeer/apps/buffer/double_buffer.go
new file mode 100644
index 00000000..63f74b4a
--- /dev/null
+++ b/vermeer/apps/buffer/double_buffer.go
@@ -0,0 +1,58 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package buffer
+
+import (
+ "fmt"
+ "sync"
+)
+
+type DoubleBuffer struct {
+ buffer [][]byte
+ current int
+ offset int
+ bufferSize int
+ locker sync.Mutex
+}
+
+func (db *DoubleBuffer) Init(size int) {
+ db.buffer = make([][]byte, 2)
+ db.buffer[0] = make([]byte, size)
+ db.buffer[1] = make([]byte, size)
+ db.current = 0
+ db.locker = sync.Mutex{}
+ db.bufferSize = size
+ db.offset = 0
+}
+
+func (db *DoubleBuffer) Write(src []byte) error {
+ db.locker.Lock()
+ defer db.locker.Unlock()
+ if len(src) > db.bufferSize {
+ return fmt.Errorf("write error, too many src bytes, %d>%d", len(src), db.bufferSize)
+ }
+ if len(src) < (db.bufferSize - db.offset) {
+ copy(db.buffer[db.current][db.offset:], src)
+ db.offset += len(src)
+ } else {
+ db.current = (db.current + 1) % 2
+ db.offset = len(src)
+ copy(db.buffer[db.current], src)
+ }
+ return nil
+}
diff --git a/vermeer/apps/buffer/encode_buffer.go b/vermeer/apps/buffer/encode_buffer.go
new file mode 100644
index 00000000..76ffc44b
--- /dev/null
+++ b/vermeer/apps/buffer/encode_buffer.go
@@ -0,0 +1,91 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package buffer
+
+import (
+ "vermeer/apps/serialize"
+
+ "github.com/sirupsen/logrus"
+)
+
+type EncodeBuffer struct {
+ buffer []byte
+ offset int
+ items int
+ threshold int
+}
+
+func (eb *EncodeBuffer) Init(size int) {
+ eb.buffer = make([]byte, size)
+ eb.offset = 0
+ eb.threshold = size - size/10
+ eb.items = 0
+}
+
+func (eb *EncodeBuffer) FromBytes(data []byte) {
+ eb.buffer = data
+ eb.offset = len(data)
+ eb.threshold = len(data)
+}
+
+func (eb *EncodeBuffer) grow(n int) {
+ logrus.Infof("buffer grow %d -> %d", len(eb.buffer), n+len(eb.buffer))
+ newBuffer := make([]byte, n+len(eb.buffer))
+ copy(newBuffer, eb.buffer)
+ eb.buffer = newBuffer
+}
+
+func (eb *EncodeBuffer) Marshal(obj serialize.MarshalAble) error {
+ if obj.PredictSize() > len(eb.buffer)-eb.offset-1024 {
+ eb.grow(obj.PredictSize() + 1024)
+ }
+ n, err := obj.Marshal(eb.buffer[eb.offset:])
+ if err != nil {
+ return err
+ }
+ eb.offset += n
+ eb.items += 1
+ return err
+}
+
+func (eb *EncodeBuffer) Unmarshal(obj serialize.MarshalAble) error {
+ n, err := obj.Unmarshal(eb.buffer[eb.offset:])
+ if err != nil {
+ return err
+ }
+ eb.offset += n
+ eb.items += 1
+ return err
+}
+
+func (eb *EncodeBuffer) PayLoad() []byte {
+ return eb.buffer[:eb.offset]
+}
+
+func (eb *EncodeBuffer) Full() bool {
+ return eb.offset >= eb.threshold
+}
+
+func (eb *EncodeBuffer) Reset() {
+ eb.offset = 0
+ eb.items = 0
+}
+
+func (eb *EncodeBuffer) ObjCount() int {
+ return eb.items
+}
diff --git a/vermeer/apps/common/base_handler.go b/vermeer/apps/common/base_handler.go
new file mode 100644
index 00000000..d7b678d9
--- /dev/null
+++ b/vermeer/apps/common/base_handler.go
@@ -0,0 +1,185 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package common
+
+import (
+ "net/http"
+ "strconv"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/sirupsen/logrus"
+)
+
+type BaseHandler interface {
+ GET(ctx *gin.Context)
+ POST(ctx *gin.Context)
+ PUT(ctx *gin.Context)
+ DELETE(ctx *gin.Context)
+}
+
+type SenHandler struct {
+ BaseHandler
+}
+
+func (s *SenHandler) GET(ctx *gin.Context) {
+ logrus.Errorf("method not implement")
+ ctx.JSON(http.StatusBadRequest, BaseResp{ErrCode: -1, Message: "method not implement"})
+}
+
+func (s *SenHandler) POST(ctx *gin.Context) {
+ logrus.Errorf("method not implement")
+ ctx.JSON(http.StatusBadRequest, BaseResp{ErrCode: -1, Message: "method not implement"})
+}
+
+func (s *SenHandler) PUT(ctx *gin.Context) {
+ logrus.Errorf("method not implement")
+ ctx.JSON(http.StatusBadRequest, BaseResp{ErrCode: -1, Message: "method not implement"})
+}
+
+func (s *SenHandler) DELETE(ctx *gin.Context) {
+ logrus.Errorf("method not implement")
+ ctx.JSON(http.StatusBadRequest, BaseResp{ErrCode: -1, Message: "method not implement"})
+}
+
+type PeriodHandler interface {
+ Init() error
+ Process()
+}
+
+type PreprocessHandler interface {
+ Init() error
+ Process()
+}
+
+type BaseResp struct {
+ ErrCode int32 `json:"errcode"`
+ Message string `json:"message,omitempty"`
+}
+
+type SenContext gin.Context
+
+func (ctx *SenContext) GetParam(key string, defValue string) string {
+ if values, ok := ctx.Request.Form[key]; ok {
+ if len(values) > 0 {
+ return values[0]
+ }
+ }
+ return defValue
+}
+
+func (ctx *SenContext) GetParamInt64(key string, defValue int64) int64 {
+ value, err := strconv.ParseInt(ctx.GetParam(key, ""), 10, 64)
+ if err != nil {
+ return defValue
+ }
+ return value
+}
+
+func (ctx *SenContext) GetParamFloat(key string, defValue float64) float64 {
+ value, err := strconv.ParseFloat(ctx.GetParam(key, ""), 64)
+ if err != nil {
+ return defValue
+ }
+ return value
+}
+
+func LoggerHandler() gin.HandlerFunc {
+ return func(ctx *gin.Context) {
+ startTime := time.Now()
+ ctx.Next()
+ endTime := time.Now()
+ latencyTime := float32(endTime.Sub(startTime)) / 1000000
+ reqMethod := ctx.Request.Method
+ reqUri := ctx.Request.URL.Path
+ statusCode := ctx.Writer.Status()
+ extInfo := ctx.GetString("log_ext_info")
+ if latencyTime > 200 {
+ logrus.Warnf("slow request: %.1fms, uri: %s", latencyTime, reqUri)
+ }
+ logrus.Infof("%3d %.1fms %s %s %s %s",
+ statusCode,
+ latencyTime,
+ ctx.ClientIP(),
+ reqMethod,
+ reqUri,
+ extInfo,
+ )
+ }
+}
+
+func ErrorHandler() gin.HandlerFunc {
+ return func(ctx *gin.Context) {
+ ctx.Next()
+ if ctx.Writer.Status() < http.StatusInternalServerError {
+ return
+ }
+ //buf := make([]byte, 1<<16)
+ //s := runtime.Stack(buf, false)
+ //dingUrl := GetConfigDefault("dingding_url", "").(string)
+ //lastDingTime := GetConfigDefault("last_ding_time", time.Unix(1085117489, 0)).(time.Time)
+ //if dingUrl != "" && time.Now().Sub(lastDingTime).Seconds() > 10 {
+ // _ = fmt.Sprintf("error, stack: %s", string(buf[:s]))
+ // // _ = DingDingSendText(dingUrl, errorInfo, nil)
+ // SetConfig("last_ding_time", time.Now())
+ //}
+ }
+}
+
+func ParamHandler() gin.HandlerFunc {
+ return func(ctx *gin.Context) {
+ req := ctx.Request
+ if err := req.ParseMultipartForm(1024 * 1024); err != nil {
+ if err != http.ErrNotMultipart {
+ logrus.Errorf("error on parse multipart form array: %v", err)
+ }
+ }
+ }
+}
+
+func ExtraHandler(data map[string]interface{}) gin.HandlerFunc {
+ return func(ctx *gin.Context) {
+ for k, v := range data {
+ ctx.Set(k, v)
+ }
+ ctx.Next()
+ }
+}
+
+type HelloHandler struct {
+ BaseHandler
+}
+
+func (h *HelloHandler) GET(ctx *gin.Context) {
+ offline := GetConfigDefault("service_offline", false).(bool)
+ if offline {
+ ctx.JSON(http.StatusInternalServerError, gin.H{
+ "code": http.StatusInternalServerError,
+ })
+ } else {
+ ctx.JSON(http.StatusOK, gin.H{
+ "code": http.StatusOK,
+ })
+ }
+}
+
+func (h *HelloHandler) POST(ctx *gin.Context) {
+ ctx.JSON(http.StatusOK, gin.H{
+ "code": http.StatusOK,
+ })
+}
diff --git a/vermeer/apps/common/configer.go b/vermeer/apps/common/configer.go
new file mode 100644
index 00000000..ec0fdda0
--- /dev/null
+++ b/vermeer/apps/common/configer.go
@@ -0,0 +1,66 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package common
+
+import (
+ "sync"
+)
+
+var stdConfig = new(Config)
+
+type Config struct {
+ data sync.Map
+}
+
+func (config *Config) Set(key string, value interface{}) {
+ config.data.Store(key, value)
+}
+
+func (config *Config) Get(key string) interface{} {
+ v, _ := config.data.Load(key)
+ return v
+}
+
+func (config *Config) GetDefault(key string, dv interface{}) interface{} {
+ v, ok := config.data.Load(key)
+ if ok {
+ return v
+ }
+ return dv
+}
+
+func ConfigerInit(src map[string]string) {
+ if src == nil {
+ return
+ }
+ for k, v := range src {
+ SetConfig(k, v)
+ }
+}
+
+func GetConfig(key string) interface{} {
+ return stdConfig.Get(key)
+}
+
+func GetConfigDefault(key string, dv interface{}) interface{} {
+ return stdConfig.GetDefault(key, dv)
+}
+
+func SetConfig(key string, value interface{}) {
+ stdConfig.Set(key, value)
+}
diff --git a/vermeer/apps/common/consts.go b/vermeer/apps/common/consts.go
new file mode 100644
index 00000000..295c9740
--- /dev/null
+++ b/vermeer/apps/common/consts.go
@@ -0,0 +1,27 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package common
+
+var ServiceName = "vermeer"
+
+type Empty struct{}
+
+const (
+ // ConfigParallelNumTaskStrategy 手动配置任务并发数量的策略
+ ConfigParallelNumTaskStrategy = "1"
+)
diff --git a/vermeer/apps/common/exp_parser.go b/vermeer/apps/common/exp_parser.go
new file mode 100644
index 00000000..640e2707
--- /dev/null
+++ b/vermeer/apps/common/exp_parser.go
@@ -0,0 +1,156 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package common
+
+import (
+ "errors"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "strconv"
+)
+
+type TNode struct {
+ Left interface{} //TNode or string
+ Right interface{} //TNode or string/float/int
+ Op token.Token
+}
+
+func CalculateForInt64(x, y interface{}, op token.Token) (bool, error) {
+
+ xInt, xok := x.(int64)
+ yInt, yok := y.(int64)
+ if !xok || !yok {
+ return false, errors.New(fmt.Sprintf("%v %v %v eval failed", x, op, y))
+ }
+
+ // 计算逻辑
+ switch op {
+ case token.EQL:
+ return xInt == yInt, nil
+ case token.NEQ:
+ return xInt != yInt, nil
+ case token.GTR:
+ return xInt > yInt, nil
+ case token.LSS:
+ return xInt < yInt, nil
+ case token.GEQ:
+ return xInt >= yInt, nil
+ case token.LEQ:
+ return xInt <= yInt, nil
+ default:
+ return false, errors.New(fmt.Sprintf("unsupported operator: %s", op.String()))
+ }
+}
+
+func CalculateForString(x, y interface{}, op token.Token) (bool, error) {
+
+ xString, xok := x.(string)
+ yString, yok := y.(string)
+ if !xok || !yok {
+ return false, errors.New(fmt.Sprintf("%v %v %v eval failed", x, op, y))
+ }
+
+ // 计算逻辑
+ switch op {
+ case token.EQL: // ==
+ return xString == yString, nil
+ case token.NEQ: // !=
+ return xString != yString, nil
+ default:
+ return false, errors.New(fmt.Sprintf("unsupported binary operator: %s", op.String()))
+ }
+
+}
+
+func Parse(expr string) (*TNode, error) {
+
+ //不过滤
+ if expr == "" {
+ return nil, nil
+ }
+ parseExpr, err := parser.ParseExpr(expr)
+ if err != nil {
+ return nil, err
+ }
+
+ r, err := eval(parseExpr)
+ if err != nil {
+ return nil, err
+ }
+
+ node, _ := r.(*TNode)
+ return node, nil
+
+}
+
+func eval(expr ast.Expr) (interface{}, error) {
+ switch e := expr.(type) {
+ case *ast.BasicLit:
+ return dealBasicLit(e)
+
+ case *ast.Ident:
+ return dealIdent(e), nil
+
+ case *ast.BinaryExpr:
+ node := new(TNode)
+ x, err := eval(e.X)
+ if err != nil {
+ return nil, err
+ }
+ node.Left = x
+
+ y, err := eval(e.Y)
+
+ if err != nil {
+ return nil, err
+ }
+
+ node.Right = y
+ node.Op = e.Op
+
+ return node, nil
+
+ default:
+ return nil, errors.New(fmt.Sprintf("expression has not support type"))
+ }
+
+}
+
+func dealBasicLit(basic *ast.BasicLit) (interface{}, error) {
+ switch basic.Kind {
+ case token.INT:
+ value, err := strconv.ParseInt(basic.Value, 10, 64)
+ if err != nil {
+ return nil, err
+ }
+ return value, nil
+ case token.STRING:
+ value, err := strconv.Unquote(basic.Value)
+ if err != nil {
+ return nil, err
+ }
+ return value, nil
+ }
+ return nil, errors.New(fmt.Sprintf("%s is not support type", basic.Kind))
+}
+
+func dealIdent(indent *ast.Ident) interface{} {
+ return indent.Name
+}
diff --git a/vermeer/apps/common/hash_util.go b/vermeer/apps/common/hash_util.go
new file mode 100644
index 00000000..fe8f0dc3
--- /dev/null
+++ b/vermeer/apps/common/hash_util.go
@@ -0,0 +1,26 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package common
+
+func HashBKDR(src string) int {
+ val := 0
+ for i := 0; i < len(src); i++ {
+ val = val*131 + int(src[i])
+ }
+ return val & 0x7FFFFFFF
+}
diff --git a/vermeer/apps/common/heap_sort.go b/vermeer/apps/common/heap_sort.go
new file mode 100644
index 00000000..15ff4496
--- /dev/null
+++ b/vermeer/apps/common/heap_sort.go
@@ -0,0 +1,49 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package common
+
+type VertexWeights []VertexWeight
+
+func (vw VertexWeights) Len() int {
+ return len(vw)
+}
+
+func (vw VertexWeights) Less(i, j int) bool {
+ return vw[i].Weight < vw[j].Weight
+}
+
+func (vw VertexWeights) Swap(i, j int) {
+ vw[i], vw[j] = vw[j], vw[i]
+}
+
+func (vw *VertexWeights) Push(x interface{}) {
+ *vw = append(*vw, x.(VertexWeight))
+}
+
+func (vw *VertexWeights) Pop() interface{} {
+ old := *vw
+ n := len(old)
+ x := old[n-1]
+ *vw = old[0 : n-1]
+ return x
+}
+
+type VertexWeight struct {
+ Vertex uint32 `json:"vertex"`
+ Weight float64 `json:"weight"`
+}
diff --git a/vermeer/apps/common/http_server.go b/vermeer/apps/common/http_server.go
new file mode 100644
index 00000000..5a5c3d28
--- /dev/null
+++ b/vermeer/apps/common/http_server.go
@@ -0,0 +1,108 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package common
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "time"
+
+ "github.com/gin-contrib/pprof"
+ "github.com/gin-gonic/gin"
+ "github.com/sirupsen/logrus"
+)
+
+type Sentinel struct {
+ engine *gin.Engine
+ srv *http.Server
+}
+
+func (sen *Sentinel) Init(peer string, debugMode string) {
+ if debugMode == "release" {
+ gin.SetMode(gin.ReleaseMode)
+ }
+
+ engine := gin.New()
+ srv := &http.Server{
+ Addr: peer,
+ Handler: engine,
+ }
+ pprof.Register(engine, "/debug/pprof")
+ engine.Use(LoggerHandler())
+ engine.Use(ErrorHandler())
+ engine.Use(gin.Recovery())
+ engine.Use(ParamHandler())
+
+ InitMetrics(engine, fmt.Sprintf("%s_%s", ServiceName, GetConfigDefault("run_mode", "").(string)))
+ sen.engine = engine
+ sen.srv = srv
+}
+
+func (sen *Sentinel) StaticFS(relativePath string, fs http.FileSystem) {
+ sen.engine.StaticFS(relativePath, fs)
+}
+func (sen *Sentinel) StaticFolder(relativePath string, folder string) {
+ sen.engine.Static(relativePath, folder)
+}
+func (sen *Sentinel) Run() {
+ if err := sen.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ logrus.Fatalf("listen error: %s", err)
+ panic(err)
+ }
+}
+
+func (sen *Sentinel) Shutdown() context.CancelFunc {
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ if err := sen.srv.Shutdown(ctx); err != nil {
+ logrus.Fatalf("Server Shutdown: %v", err)
+ }
+ return cancel
+}
+
+func (sen *Sentinel) Register(routers map[string]BaseHandler) {
+ for url, handler := range routers {
+ sen.engine.GET(url, handler.GET)
+ sen.engine.POST(url, handler.POST)
+ sen.engine.PUT(url, handler.PUT)
+ sen.engine.DELETE(url, handler.DELETE)
+ }
+}
+
+func (sen *Sentinel) RegisterGroup(base string, routers map[string]BaseHandler, filters ...gin.HandlerFunc) {
+ rg := sen.engine.Group(base, filters...)
+ for url, handler := range routers {
+ rg.GET(url, handler.GET)
+ rg.POST(url, handler.POST)
+ rg.PUT(url, handler.PUT)
+ rg.DELETE(url, handler.DELETE)
+ }
+}
+
+func (sen *Sentinel) RegisterAPI(verion int, base string, routers map[string]BaseHandler, filters ...gin.HandlerFunc) {
+ root := fmt.Sprintf("/api/v%d%s", verion, base)
+ rg := sen.engine.Group(root, filters...)
+
+ for url, handler := range routers {
+ rg.GET(url, handler.GET)
+ rg.POST(url, handler.POST)
+ rg.PUT(url, handler.PUT)
+ rg.DELETE(url, handler.DELETE)
+ }
+
+}
diff --git a/vermeer/apps/common/hugegraph_tools.go b/vermeer/apps/common/hugegraph_tools.go
new file mode 100644
index 00000000..c403fa3a
--- /dev/null
+++ b/vermeer/apps/common/hugegraph_tools.go
@@ -0,0 +1,345 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package common
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/sirupsen/logrus"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials/insecure"
+ "google.golang.org/grpc/metadata"
+
+ pd "vermeer/apps/protos/hugegraph-pd-grpc"
+ hstore "vermeer/apps/protos/hugegraph-store-grpc"
+)
+
+const (
+ HgVertexIDTypeString HgVertexIDType = iota
+ HgVertexIDTypeNumber
+)
+
+const (
+ HgValueTypeInt = "INT"
+ HgValueTypeFloat = "FLOAT"
+ HgValueTypeString = "TEXT"
+)
+
+type HgVertexIDType uint16
+
+func FindValidPD(pdIPAddress []string) (string, error) {
+ // retry 3 times to find valid pd address
+ var pdIPAddr string
+ var err error
+ for i := 0; i < 3; i++ {
+ pdIPAddr, err = findValidPD(pdIPAddress)
+ if err == nil && pdIPAddr != "" {
+ return pdIPAddr, nil
+ }
+ logrus.Errorf("find pd address error:%v", err)
+ time.Sleep(500 * time.Millisecond)
+ }
+ return pdIPAddr, err
+}
+
+func findValidPD(pdIPAddress []string) (string, error) {
+ // 找出有效的pd address
+ wg := &sync.WaitGroup{}
+ var pdIPAddr string
+ tempCtx, tempCancel := context.WithTimeout(context.Background(), 5*time.Second)
+ pdAuthority := PDAuthority{}
+ tempCtx = pdAuthority.SetAuthority(tempCtx)
+ for _, addr := range pdIPAddress {
+ wg.Add(1)
+ go func(ctx context.Context, cancel context.CancelFunc, addr string) {
+ defer wg.Done()
+ var md metadata.MD
+ pdConn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
+ if err != nil {
+ logrus.Errorf("grpc dial error:%v", err)
+ return
+ }
+ pdClient := pd.NewPDClient(pdConn)
+ _, err = pdClient.GetMembers(ctx, &pd.GetMembersRequest{}, grpc.Header(&md))
+ pdAuthority.SetToken(md)
+ _ = pdConn.Close()
+ if err == nil {
+ pdIPAddr = addr
+ cancel()
+ }
+ }(tempCtx, tempCancel, addr)
+ }
+ wg.Wait()
+ tempCancel()
+ if len(pdIPAddr) == 0 {
+ logrus.Errorf("hugegraph.pd_address unable to connect :%v", pdIPAddress)
+ return "", errors.New("hugegraph.pd_address unable to connect")
+ }
+ return pdIPAddr, nil
+}
+
+func FindServerAddr(pdIPAddress string, hgName string, username string, password string) (string, error) {
+ // retry 3 times to find valid server address
+ var serverAddr string
+ var err error
+ for i := 0; i < 3; i++ {
+ serverAddr, err = findServerAddr(pdIPAddress, hgName, username, password)
+ if err == nil && serverAddr != "" {
+ return serverAddr, nil
+ }
+ logrus.Errorf("find server address error:%v", err)
+ time.Sleep(500 * time.Millisecond)
+ }
+ return serverAddr, err
+}
+
+func findServerAddr(pdIPAddress string, hgName string, username string, password string) (string, error) {
+ // 找出有效的server address
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ pdConn, err := grpc.Dial(pdIPAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
+ if err != nil {
+ logrus.Errorf("dial pd error:%v", err)
+ return "", err
+ }
+ defer pdConn.Close()
+ pdAuthority := PDAuthority{}
+ ctx = pdAuthority.SetAuthority(ctx)
+ pdClient := pd.NewDiscoveryServiceClient(pdConn)
+ resp, err := pdClient.GetNodes(ctx, &pd.Query{})
+ if err != nil {
+ return "", err
+ }
+
+ sameSpaceServer := make([]string, 0)
+ defaultSpaceServer := make([]string, 0)
+ otherServer := make([]string, 0)
+ hgSpace, hGraph, err := SplitHgName(hgName)
+ if err != nil {
+ return "", err
+ }
+ for _, info := range resp.GetInfo() {
+ if info.GetLabels()["GRAPHSPACE"] == hgSpace {
+ sameSpaceServer = append(sameSpaceServer, info.Address)
+ } else if info.GetLabels()["GRAPHSPACE"] == "DEFAULT" {
+ defaultSpaceServer = append(defaultSpaceServer, info.Address)
+ } else {
+ otherServer = append(otherServer, info.Address)
+ }
+ }
+ // 先校验图空间名字对应的server地址
+ validServerAddr := testServerIsValid(sameSpaceServer, hgSpace, hGraph, username, password)
+ // 其次再校验默认图空间
+ if len(validServerAddr) == 0 {
+ validServerAddr = testServerIsValid(defaultSpaceServer, hgSpace, hGraph, username, password)
+ }
+ // 最后校验其他server是否可用
+ if len(validServerAddr) == 0 {
+ validServerAddr = testServerIsValid(otherServer, hgSpace, hGraph, username, password)
+ }
+ if len(validServerAddr) == 0 {
+ logrus.Errorf("hugegraph server address unable to connect :%v,%v,%v", sameSpaceServer, defaultSpaceServer, otherServer)
+ return "", errors.New("hugegraph server address unable to connect")
+ }
+ return validServerAddr, nil
+}
+
+func testServerIsValid(serverAdds []string, hgSpace, hGraph, username, password string) string {
+ tempCtx, tempCancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer tempCancel()
+ var validServerAddr string
+ wg := &sync.WaitGroup{}
+ for _, addr := range serverAdds {
+ wg.Add(1)
+ go func(addr string, ctx context.Context, cancel context.CancelFunc) {
+ defer wg.Done()
+ url := fmt.Sprintf("%v/graphspaces/%v/graphs/%v/schema?format=json", addr, hgSpace, hGraph)
+ req, err := http.NewRequest(http.MethodGet, url, nil)
+ req.SetBasicAuth(username, password)
+ if err != nil {
+ return
+ }
+ req.WithContext(ctx)
+ client := &http.Client{Timeout: 5 * time.Second}
+ resp, err := client.Do(req)
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode == http.StatusOK {
+ validServerAddr = addr
+ cancel()
+ return
+ }
+ }(addr, tempCtx, tempCancel)
+ }
+ wg.Wait()
+ return validServerAddr
+}
+
+func GetHugegraphSchema(serverAddr, hgName, username, password string) (map[string]any, error) {
+ hgSpace, hGraph, err := SplitHgName(hgName)
+ if err != nil {
+ logrus.Errorf("split hg name failed, %v", err.Error())
+ return nil, fmt.Errorf("split hg name failed, %v", err.Error())
+ }
+ url := fmt.Sprintf("%v/graphspaces/%v/graphs/%v/schema?format=json", serverAddr, hgSpace, hGraph)
+ // retry 3 times
+ var propertyFromHg map[string]any
+ for i := 0; i < 3; i++ {
+ propertyFromHg, err = getHugegraphSchema(url, username, password)
+ if err == nil {
+ return propertyFromHg, nil
+ }
+ logrus.Errorf("get schema failed, %v", err.Error())
+ time.Sleep(500 * time.Millisecond)
+ }
+ return nil, err
+}
+
+func getHugegraphSchema(url, username, password string) (map[string]any, error) {
+ req, err := http.NewRequest(http.MethodGet, url, nil)
+ req.SetBasicAuth(username, password)
+ if err != nil {
+ logrus.Errorf("create http request failed, %v", err.Error())
+ return nil, fmt.Errorf("create http request failed, %v", err.Error())
+ }
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ req.WithContext(ctx)
+ client := &http.Client{Timeout: 5 * time.Second}
+ resp, err := client.Do(req)
+ if err != nil {
+ logrus.Errorf("http request failed, %v", err.Error())
+ return nil, fmt.Errorf("http request failed, %v", err.Error())
+ }
+ defer resp.Body.Close()
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, fmt.Errorf("read response body failed, %v", err.Error())
+ }
+ if resp.StatusCode != http.StatusOK {
+ logrus.Errorf("response status not 200 OK, Get:%v", resp.Status)
+ return nil, fmt.Errorf("get schema failed, status code:%v, body:%v", resp.StatusCode, string(body))
+ }
+ propertyFromHg := make(map[string]any)
+ err = json.Unmarshal(body, &propertyFromHg)
+ if err != nil {
+ logrus.Errorf("unmarshal response body failed, %v", err.Error())
+ return nil, fmt.Errorf("unmarshal response body failed, %v", err.Error())
+ }
+ return propertyFromHg, nil
+}
+
+func SplitHgName(hgName string) (string, string, error) {
+ hgSplit := strings.Split(hgName, "/")
+ if len(hgSplit) != 3 {
+ return "", "", fmt.Errorf("hugegraph name not correct:%v", hgName)
+ }
+ return hgSplit[0], hgSplit[1], nil
+}
+
+func VariantToInt(variant *hstore.Variant) (int32, error) {
+ switch *variant.Type {
+ case hstore.VariantType_VT_INT:
+ return variant.GetValueInt32(), nil
+ case hstore.VariantType_VT_LONG:
+ return int32(variant.GetValueInt64()), nil
+ }
+ return 0, fmt.Errorf("hstore variant type wrong :%v", *variant.Type)
+}
+
+func VariantToFloat(variant *hstore.Variant) (float32, error) {
+ switch *variant.Type {
+ case hstore.VariantType_VT_FLOAT:
+ return variant.GetValueFloat(), nil
+ case hstore.VariantType_VT_DOUBLE:
+ return float32(variant.GetValueDouble()), nil
+ }
+ return 0, fmt.Errorf("hstore variant type wrong :%v", *variant.Type)
+}
+
+func VariantToString(variant *hstore.Variant) (string, error) {
+ switch *variant.Type {
+ case hstore.VariantType_VT_BOOLEAN:
+ if variant.GetValueBoolean() {
+ return "true", nil
+ }
+ return "false", nil
+ case hstore.VariantType_VT_INT:
+ return strconv.FormatInt(int64(variant.GetValueInt32()), 10), nil
+ case hstore.VariantType_VT_LONG:
+ return strconv.FormatInt(variant.GetValueInt64(), 10), nil
+ case hstore.VariantType_VT_FLOAT:
+ return strconv.FormatFloat(float64(variant.GetValueFloat()), 'E', -1, 32), nil
+ case hstore.VariantType_VT_DOUBLE:
+ return strconv.FormatFloat(variant.GetValueDouble(), 'E', -1, 64), nil
+ case hstore.VariantType_VT_STRING:
+ return variant.GetValueString(), nil
+ case hstore.VariantType_VT_BYTES:
+ return string(variant.GetValueBytes()), nil
+ case hstore.VariantType_VT_DATETIME:
+ return variant.GetValueDatetime(), nil
+ }
+ return "", fmt.Errorf("hstore variant type wrong :%v", *variant.Type)
+}
+
+const (
+ CredentialKey = "credential"
+ CredentialValue = "dmVybWVlcjokMmEkMDQkTjg5cUhlMHY1anFOSktoUVpIblRkT0ZTR21pTm9pQTJCMmZkV3BWMkJ3cnRKSzcyZFhZRC4="
+ TokenKey = "Pd-Token"
+)
+
+type PDAuthority struct {
+ token string
+ locker sync.RWMutex
+}
+
+func (pa *PDAuthority) SetAuthority(ctx context.Context) context.Context {
+ pa.locker.RLock()
+ defer pa.locker.RUnlock()
+ md := metadata.New(map[string]string{
+ CredentialKey: CredentialValue,
+ })
+ if pa.token != "" {
+ md.Set(TokenKey, pa.token)
+ }
+ // logrus.Infof("send md:%v", md)
+ ctx = metadata.NewOutgoingContext(ctx, md)
+ return ctx
+}
+
+func (pa *PDAuthority) SetToken(md metadata.MD) {
+ pa.locker.Lock()
+ defer pa.locker.Unlock()
+ // logrus.Infof("recv md:%v", md)
+ token := md.Get(TokenKey)
+ if len(token) == 1 {
+ pa.token = token[0]
+ }
+}
diff --git a/vermeer/apps/common/locker.go b/vermeer/apps/common/locker.go
new file mode 100644
index 00000000..85fdd92b
--- /dev/null
+++ b/vermeer/apps/common/locker.go
@@ -0,0 +1,68 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package common
+
+import (
+ "sync/atomic"
+ "time"
+)
+
+type SpinLocker uint32
+
+func (sl *SpinLocker) Lock() {
+ for i := 0; ; i++ {
+ if atomic.CompareAndSwapUint32((*uint32)(sl), 0, 1) {
+ break
+ }
+ if i >= 1000 {
+ i = 0
+ //logrus.Warnf("spin lock busy")
+ time.Sleep(1 * time.Millisecond)
+ }
+ }
+}
+
+func (sl *SpinLocker) UnLock() {
+ atomic.StoreUint32((*uint32)(sl), 0)
+}
+
+type SpinWaiter int32
+
+func (sw *SpinWaiter) Reset() {
+ atomic.StoreInt32((*int32)(sw), 0)
+}
+
+func (sw *SpinWaiter) Add(n int32) {
+ atomic.AddInt32((*int32)(sw), n)
+}
+
+func (sw *SpinWaiter) Done() {
+ sw.Add(-1)
+}
+
+func (sw *SpinWaiter) Wait() {
+ for i := 0; ; i++ {
+ if atomic.LoadInt32((*int32)(sw)) <= 0 {
+ break
+ }
+ if i >= 1000 {
+ i = 0
+ time.Sleep(1 * time.Millisecond)
+ }
+ }
+}
diff --git a/vermeer/apps/common/log_formatter.go b/vermeer/apps/common/log_formatter.go
new file mode 100644
index 00000000..551a894d
--- /dev/null
+++ b/vermeer/apps/common/log_formatter.go
@@ -0,0 +1,35 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package common
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/sirupsen/logrus"
+)
+
+type MyFormatter struct {
+}
+
+func (f *MyFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+ timestamp := time.Now().Local().Format("06-01-02 15:04:05")
+ msg := fmt.Sprintf("%s [%s] \"%s\"\n", timestamp, strings.ToUpper(entry.Level.String()), entry.Message)
+ return []byte(msg), nil
+}
diff --git a/vermeer/apps/common/metrics.go b/vermeer/apps/common/metrics.go
new file mode 100644
index 00000000..98ce2b68
--- /dev/null
+++ b/vermeer/apps/common/metrics.go
@@ -0,0 +1,168 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package common
+
+import (
+ "strconv"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promhttp"
+)
+
+var PrometheusMetrics MozartPrometheus
+
+type MozartPrometheus struct {
+ serviceName string
+ reqCnt *prometheus.CounterVec
+ reqDur *prometheus.SummaryVec
+ slowReqCnt *prometheus.CounterVec
+ TaskCnt *prometheus.CounterVec
+ TaskRunningCnt *prometheus.GaugeVec
+ GraphCnt *prometheus.GaugeVec
+ GraphLoadedCnt *prometheus.GaugeVec
+ WorkerCnt *prometheus.GaugeVec
+ VertexCnt *prometheus.GaugeVec
+ EdgeCnt *prometheus.GaugeVec
+}
+
+func (p *MozartPrometheus) HandleFunc() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ start := time.Now()
+ c.Next()
+ status := strconv.Itoa(c.Writer.Status())
+ elapsed := time.Since(start) / time.Millisecond
+ reqUri := c.Request.URL.Path
+ action := c.DefaultQuery("action", "")
+ reqUri += "/" + action
+ p.reqCnt.WithLabelValues(status, c.Request.Method, c.Request.Host, reqUri).Inc()
+ p.reqDur.WithLabelValues(status, c.Request.Method, c.Request.Host, reqUri).Observe(float64(elapsed))
+ if elapsed > 20 {
+ p.slowReqCnt.WithLabelValues(c.Request.Host, reqUri, "20").Inc()
+ }
+ if elapsed > 200 {
+ p.slowReqCnt.WithLabelValues(c.Request.Host, reqUri, "200").Inc()
+ }
+ }
+}
+
+func (p *MozartPrometheus) register() {
+ _ = prometheus.Register(p.reqCnt)
+ _ = prometheus.Register(p.reqDur)
+ _ = prometheus.Register(p.slowReqCnt)
+ _ = prometheus.Register(p.TaskCnt)
+ _ = prometheus.Register(p.TaskRunningCnt)
+ _ = prometheus.Register(p.GraphCnt)
+ _ = prometheus.Register(p.GraphLoadedCnt)
+ _ = prometheus.Register(p.WorkerCnt)
+ _ = prometheus.Register(p.VertexCnt)
+ _ = prometheus.Register(p.EdgeCnt)
+}
+
+func InitMetrics(engine *gin.Engine, serviceName string) {
+ reqCnt := prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Subsystem: serviceName,
+ Name: "requests_total",
+ Help: "How many HTTP requests processed, partitioned by status code and HTTP method.",
+ },
+ []string{"code", "method", "host", "url"})
+ reqDur := prometheus.NewSummaryVec(
+ prometheus.SummaryOpts{
+ Subsystem: serviceName,
+ Name: "request_duration_ms",
+ Help: "The HTTP request latencies in ms.",
+ Objectives: map[float64]float64{0.9: 0, 0.95: 0, 0.99: 0},
+ MaxAge: 10 * time.Second,
+ AgeBuckets: 3,
+ },
+ []string{"code", "method", "host", "url"})
+ slowReqCnt := prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Subsystem: serviceName,
+ Name: "requests_slow",
+ Help: "How many HTTP requests longer than xx ms.",
+ },
+ []string{"host", "url", "level"})
+ taskCnt := prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Subsystem: serviceName,
+ Name: "task_count_total",
+ Help: "How many tasks created, partitioned by task type.",
+ },
+ []string{"task_type"})
+ taskRunningCnt := prometheus.NewGaugeVec(
+ prometheus.GaugeOpts{
+ Subsystem: serviceName,
+ Name: "task_running_total",
+ Help: "How many tasks is running, partitioned by task type.",
+ },
+ []string{"task_type"})
+ graphCnt := prometheus.NewGaugeVec(
+ prometheus.GaugeOpts{
+ Subsystem: serviceName,
+ Name: "graph_total",
+ Help: "How many graphs is created",
+ },
+ []string{})
+ graphLoadedCnt := prometheus.NewGaugeVec(
+ prometheus.GaugeOpts{
+ Subsystem: serviceName,
+ Name: "graph_loaded_total",
+ Help: "How many graphs is loaded",
+ },
+ []string{})
+ workerCnt := prometheus.NewGaugeVec(
+ prometheus.GaugeOpts{
+ Subsystem: serviceName,
+ Name: "worker_count",
+ Help: "How many workers is available",
+ },
+ []string{})
+ vertexCnt := prometheus.NewGaugeVec(
+ prometheus.GaugeOpts{
+ Subsystem: serviceName,
+ Name: "vertex_count",
+ Help: "How many vertices in graphs, partitioned by graph name.",
+ },
+ []string{"graph_name"})
+ edgeCnt := prometheus.NewGaugeVec(
+ prometheus.GaugeOpts{
+ Subsystem: serviceName,
+ Name: "edge_count",
+ Help: "How many edges in graphs, partitioned by graph name.",
+ },
+ []string{"graph_name"})
+ PrometheusMetrics = MozartPrometheus{
+ serviceName: serviceName,
+ reqCnt: reqCnt,
+ reqDur: reqDur,
+ slowReqCnt: slowReqCnt,
+ TaskCnt: taskCnt,
+ TaskRunningCnt: taskRunningCnt,
+ GraphCnt: graphCnt,
+ GraphLoadedCnt: graphLoadedCnt,
+ WorkerCnt: workerCnt,
+ VertexCnt: vertexCnt,
+ EdgeCnt: edgeCnt,
+ }
+ PrometheusMetrics.register()
+ engine.Use(PrometheusMetrics.HandleFunc())
+ engine.GET("/metrics", gin.WrapH(promhttp.Handler()))
+}
diff --git a/vermeer/apps/common/os_util.go b/vermeer/apps/common/os_util.go
new file mode 100644
index 00000000..64ff4c84
--- /dev/null
+++ b/vermeer/apps/common/os_util.go
@@ -0,0 +1,66 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package common
+
+import (
+ "os"
+ "path/filepath"
+
+ "github.com/shirou/gopsutil/v3/mem"
+ "github.com/shirou/gopsutil/v3/process"
+ "github.com/sirupsen/logrus"
+)
+
+func AppRootPath() string {
+ path, err := GetCurrentPath()
+ if err != nil {
+ logrus.Errorf("failed to get `AppRootPath`, err: %v", err)
+ return ""
+ }
+ return path
+}
+
+func GetCurrentPath() (string, error) {
+ ex, err := os.Executable()
+ if err != nil {
+ return "", err
+ }
+ currentPath := filepath.Dir(ex)
+ return currentPath, nil
+}
+
+func IsFileOrDirExist(dir string) bool {
+ _, err := os.Stat(dir)
+ return err == nil
+}
+
+func MachineMemUsedPercent() (float64, error) {
+ v, err := mem.VirtualMemory()
+ return v.UsedPercent, err
+}
+
+func ProcessMemUsedPercent() (float64, error) {
+ pid := os.Getpid()
+ proc, err := process.NewProcess(int32(pid))
+
+ if err != nil {
+ return 0, err
+ }
+ p, err := proc.MemoryPercent()
+ return float64(p), err
+}
diff --git a/vermeer/apps/common/panic_stack.go b/vermeer/apps/common/panic_stack.go
new file mode 100644
index 00000000..aa55024b
--- /dev/null
+++ b/vermeer/apps/common/panic_stack.go
@@ -0,0 +1,31 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package common
+
+import "runtime"
+
+const (
+ defaultStackSize = 4096
+)
+
+// GetCurrentGoroutineStack 获取当前Goroutine的调用栈,便于排查panic异常
+func GetCurrentGoroutineStack() string {
+ var buf [defaultStackSize]byte
+ n := runtime.Stack(buf[:], false)
+ return string(buf[:n])
+}
diff --git a/vermeer/apps/common/queue.go b/vermeer/apps/common/queue.go
new file mode 100644
index 00000000..a7688d9e
--- /dev/null
+++ b/vermeer/apps/common/queue.go
@@ -0,0 +1,134 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package common
+
+import (
+ "sync"
+)
+
+// QueueNode 代表每一个节点
+type QueueNode struct {
+ Data interface{}
+ Next *QueueNode
+}
+
+type Queue struct {
+ // 头节点
+ Head *QueueNode
+
+ // 队尾节点
+ Tail *QueueNode
+
+ Size int
+
+ locker sync.Mutex
+}
+
+func NewQueue() *Queue {
+ q := new(Queue)
+ q.Head = nil
+ q.Tail = nil
+ q.Size = 0
+ q.locker = sync.Mutex{}
+ return q
+}
+
+// Put 尾插法
+func (q *Queue) Put(element interface{}) {
+ q.locker.Lock()
+ defer q.locker.Unlock()
+
+ n := new(QueueNode)
+ n.Data = element
+
+ if q.Tail == nil {
+ q.Head = n
+ q.Tail = n
+ } else {
+ q.Tail.Next = n
+ q.Tail = n
+ }
+ q.Size++
+}
+
+// PutHead 头插法,在队列头部插入一个元素
+func (q *Queue) PutHead(element interface{}) {
+ q.locker.Lock()
+ defer q.locker.Unlock()
+
+ n := new(QueueNode)
+ n.Data = element
+
+ if q.Head == nil {
+ q.Head = n
+ q.Tail = n
+ } else {
+ n.Next = q.Head
+ q.Head = n
+ }
+ q.Size++
+}
+
+// Poll 获取并删除队列头部的元素
+func (q *Queue) Poll() interface{} {
+ q.locker.Lock()
+ defer q.locker.Unlock()
+
+ if q.Head == nil {
+ return nil
+ }
+ n := q.Head
+ // 代表队列中仅一个元素
+ if n.Next == nil {
+ q.Head = nil
+ q.Tail = nil
+
+ } else {
+ q.Head = n.Next
+ }
+ q.Size--
+ return n.Data
+}
+
+func (q *Queue) Peek() interface{} {
+ q.locker.Lock()
+ defer q.locker.Unlock()
+
+ if q.Head == nil {
+ return nil
+ }
+
+ return q.Head.Data
+}
+
+func (q *Queue) PeekTail() interface{} {
+ q.locker.Lock()
+ defer q.locker.Unlock()
+
+ if q.Tail == nil {
+ return nil
+ }
+
+ return q.Tail.Data
+}
+
+func (q *Queue) Length() int {
+ q.locker.Lock()
+ defer q.locker.Unlock()
+ return q.Size
+}
diff --git a/vermeer/apps/common/string_util.go b/vermeer/apps/common/string_util.go
new file mode 100644
index 00000000..c3c4a6c9
--- /dev/null
+++ b/vermeer/apps/common/string_util.go
@@ -0,0 +1,57 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package common
+
+import (
+ "fmt"
+ "runtime"
+ "strconv"
+ "strings"
+)
+
+func ItoaPad(i int, pad int) string {
+ result := strconv.Itoa(i)
+ if len(result) < pad {
+ result = strings.Repeat("0", pad-len(result)) + result
+ }
+ return result
+}
+
+func PrintMemUsage() string {
+ var m runtime.MemStats
+ runtime.ReadMemStats(&m)
+ alloc := ByteToHuman(m.Alloc)
+ totalAlloc := ByteToHuman(m.TotalAlloc)
+ sys := ByteToHuman(m.Sys)
+ info := fmt.Sprintf("alloc: %v, total: %v, sys: %v, gc: %v", alloc, totalAlloc, sys, m.NumGC)
+ return info
+}
+
+func ByteToHuman(b uint64) string {
+ h := float64(b)
+ units := []string{"B", "KB", "MB", "GB", "TB", "PB"}
+ unit := 0
+ for {
+ if h/1024.0 < 1.0 || unit >= len(units)-1 {
+ break
+ }
+ unit++
+ h /= 1024
+ }
+ return fmt.Sprintf("%.3f %s", h, units[unit])
+}
diff --git a/vermeer/apps/compute/api.go b/vermeer/apps/compute/api.go
new file mode 100644
index 00000000..3979a201
--- /dev/null
+++ b/vermeer/apps/compute/api.go
@@ -0,0 +1,560 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package compute
+
+import (
+ "context"
+ "fmt"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "plugin"
+ "strings"
+ "vermeer/apps/buffer"
+ "vermeer/apps/serialize"
+ "vermeer/apps/structure"
+
+ "github.com/sirupsen/logrus"
+)
+
+type ValueType int
+
+const (
+ ValueTypeFloat32 ValueType = iota
+ ValueTypeInt32
+ ValueTypeInt64
+ ValueTypeSliceUint32
+ ValueTypeSliceFloat32
+ ValueTypeSliceInt64
+)
+
+const (
+ CValueActionNormal = iota
+ CValueActionAggregate
+)
+
+const (
+ UseOutEdge = "use_out_edge"
+ UseOutDegree = "use_out_degree"
+ UseProperty = "use_property"
+)
+
+type AlgorithmType int
+
+const (
+ AlgorithmOLAP AlgorithmType = iota
+ AlgorithmOLTP
+)
+
+type CValue struct {
+ ValueType ValueType
+ Action byte
+ Value interface{}
+}
+
+func (cv *CValue) Marshal(buffer []byte) (int, error) {
+ offset := 0
+ buffer[offset] = byte(cv.ValueType)
+ offset++
+ buffer[offset] = cv.Action
+ offset++
+ switch cv.ValueType {
+ case ValueTypeInt32:
+ v := cv.Value.(serialize.SInt32)
+ n, _ := v.Marshal(buffer[offset:])
+ offset += n
+ case ValueTypeInt64:
+ v := cv.Value.(serialize.SInt64)
+ n, _ := v.Marshal(buffer[offset:])
+ offset += n
+ case ValueTypeFloat32:
+ v := cv.Value.(serialize.SFloat32)
+ n, _ := v.Marshal(buffer[offset:])
+ offset += n
+ case ValueTypeSliceUint32:
+ v := cv.Value.(serialize.SliceUint32)
+ n, _ := v.Marshal(buffer[offset:])
+ offset += n
+ case ValueTypeSliceFloat32:
+ v := cv.Value.(serialize.SliceFloat32)
+ n, _ := v.Marshal(buffer[offset:])
+ offset += n
+ case ValueTypeSliceInt64:
+ v := cv.Value.(serialize.SliceInt64)
+ n, _ := v.Marshal(buffer[offset:])
+ offset += n
+ default:
+ logrus.Errorf("Marshal value type not matched: %d", cv.ValueType)
+ }
+
+ return offset, nil
+}
+
+func (cv *CValue) Unmarshal(buffer []byte) (int, error) {
+ offset := 0
+ cv.ValueType = ValueType(buffer[offset])
+ offset++
+ cv.Action = buffer[offset]
+ offset++
+
+ switch cv.ValueType {
+ case ValueTypeInt32:
+ var vv serialize.SInt32
+ n, _ := vv.Unmarshal(buffer[offset:])
+ cv.Value = vv
+ offset += n
+ case ValueTypeInt64:
+ var vv serialize.SInt64
+ n, _ := vv.Unmarshal(buffer[offset:])
+ cv.Value = vv
+ offset += n
+ case ValueTypeFloat32:
+ var vv serialize.SFloat32
+ n, _ := vv.Unmarshal(buffer[offset:])
+ cv.Value = vv
+ offset += n
+ case ValueTypeSliceUint32:
+ var vv serialize.SliceUint32
+ n, _ := vv.Unmarshal(buffer[offset:])
+ cv.Value = vv
+ offset += n
+ case ValueTypeSliceFloat32:
+ var vv serialize.SliceFloat32
+ n, _ := vv.Unmarshal(buffer[offset:])
+ cv.Value = vv
+ offset += n
+ case ValueTypeSliceInt64:
+ var vv serialize.SliceInt64
+ n, _ := vv.Unmarshal(buffer[offset:])
+ cv.Value = vv
+ offset += n
+ default:
+ logrus.Errorf("Unmarshal value type not matched: %d", cv.ValueType)
+ }
+
+ return offset, nil
+}
+
+func (cv *CValue) ToString() string {
+ var toString string
+ switch cv.ValueType {
+ case ValueTypeInt32:
+ value := cv.Value.(serialize.SInt32)
+ toString = value.ToString()
+ case ValueTypeInt64:
+ value := cv.Value.(serialize.SInt64)
+ toString = value.ToString()
+ case ValueTypeFloat32:
+ value := cv.Value.(serialize.SFloat32)
+ toString = value.ToString()
+ case ValueTypeSliceUint32:
+ value := cv.Value.(serialize.SliceUint32)
+ toString = value.ToString()
+ case ValueTypeSliceFloat32:
+ value := cv.Value.(serialize.SFloat32)
+ toString = value.ToString()
+ case ValueTypeSliceInt64:
+ value := cv.Value.(serialize.SliceInt64)
+ toString = value.ToString()
+ default:
+ logrus.Errorf("Unmarshal value type not matched: %d", cv.ValueType)
+ }
+ return toString
+}
+
+func (cv *CValue) PredictSize() int {
+ var predictSize int
+ predictSize += 2
+ switch cv.ValueType {
+ case ValueTypeInt32:
+ value := cv.Value.(serialize.SInt32)
+ predictSize = value.PredictSize()
+ case ValueTypeInt64:
+ value := cv.Value.(serialize.SInt64)
+ predictSize = value.PredictSize()
+ case ValueTypeFloat32:
+ value := cv.Value.(serialize.SFloat32)
+ predictSize = value.PredictSize()
+ case ValueTypeSliceUint32:
+ value := cv.Value.(serialize.SliceUint32)
+ predictSize = value.PredictSize()
+ case ValueTypeSliceFloat32:
+ value := cv.Value.(serialize.SliceFloat32)
+ predictSize = value.PredictSize()
+ case ValueTypeSliceInt64:
+ value := cv.Value.(serialize.SliceInt64)
+ predictSize = value.PredictSize()
+ default:
+ logrus.Errorf("Unmarshal value type not matched: %d", cv.ValueType)
+ }
+ return predictSize
+}
+
+type CContext struct {
+ context.Context
+ CValues map[string]*CValue
+}
+
+func (cc *CContext) CreateValue(name string, vType ValueType, action byte) {
+ vv := CValue{
+ ValueType: vType,
+ Action: action,
+ }
+
+ cc.CValues[name] = &vv
+}
+
+func (cc *CContext) SetValue(name string, value interface{}) {
+ cc.CValues[name].Value = value
+}
+
+func (cc *CContext) GetValue(name string) interface{} {
+ return cc.CValues[name].Value
+}
+
+func (cc *CContext) MarshalValues() map[string][]byte {
+ cValues := make(map[string][]byte, len(cc.CValues))
+ for k, v := range cc.CValues {
+ aa := make([]byte, v.PredictSize()+64)
+ n, _ := v.Marshal(aa)
+ bb := make([]byte, n)
+ copy(bb, aa)
+ cValues[k] = bb
+ // logrus.Infof("context marshal value: %s, %v", k, v.Value)
+ }
+ return cValues
+}
+
+func (cc *CContext) UnmarshalValues(values map[string][]byte) {
+ for k, v := range cc.CValues {
+ if data, ok := values[k]; ok {
+ _, err := v.Unmarshal(data)
+ if err != nil {
+ logrus.Errorf("compute value unmarshal error: %s", err)
+ }
+ // logrus.Infof("context unmarshal value: %s, %v", k, v.Value)
+ }
+ }
+}
+
+type VValues struct {
+ VType ValueType
+ Values interface{}
+}
+
+type CWContext struct {
+ CContext
+ Params map[string]string
+ Step int32
+ Parallel int
+ Workers int
+ GraphData *structure.GraphData
+ SendBuffers []buffer.EncodeBuffer
+ WorkerIdx int
+ Output bool
+}
+
+func (cwc *CWContext) MakeSendBuffer() {
+ cwc.SendBuffers = make([]buffer.EncodeBuffer, cwc.Parallel)
+ for i := range cwc.SendBuffers {
+ cwc.SendBuffers[i].Init(2 * 1024 * 1024)
+ }
+}
+
+func (cwc *CWContext) PartCount(total int) int {
+ if cwc.WorkerIdx+1 == cwc.Workers {
+ return total - (total/cwc.Workers)*(cwc.Workers-1)
+ }
+
+ return total / cwc.Workers
+}
+
+func (cwc *CWContext) PartStart(total int) int {
+ return (total / cwc.Workers) * cwc.WorkerIdx
+}
+
+type CMContext struct {
+ CContext
+ Params map[string]string
+ Step int32
+ Parallel int
+ WorkerCValues map[string]map[string]*CValue
+}
+
+func (cmc *CMContext) AggregateValue() {
+ cValues := make(map[string]*CValue)
+ for _, v := range cmc.WorkerCValues {
+ for vn, vv := range v {
+ if vv.Action != CValueActionAggregate {
+ continue
+ }
+ if cv, ok := cValues[vn]; !ok {
+ cValues[vn] = &CValue{
+ ValueType: vv.ValueType,
+ Action: vv.Action,
+ Value: vv.Value,
+ }
+ } else {
+ switch cv.ValueType {
+ case ValueTypeFloat32:
+ cValues[vn].Value = cValues[vn].Value.(serialize.SFloat32) + vv.Value.(serialize.SFloat32)
+ case ValueTypeInt32:
+ cValues[vn].Value = cValues[vn].Value.(serialize.SInt32) + vv.Value.(serialize.SInt32)
+ case ValueTypeInt64:
+ cValues[vn].Value = cValues[vn].Value.(serialize.SInt64) + vv.Value.(serialize.SInt64)
+ case ValueTypeSliceUint32:
+ cValues[vn].Value = append(cValues[vn].Value.(serialize.SliceUint32),
+ vv.Value.(serialize.SliceUint32)...)
+ case ValueTypeSliceFloat32:
+ cValues[vn].Value = append(cValues[vn].Value.(serialize.SliceFloat32),
+ vv.Value.(serialize.SliceFloat32)...)
+ case ValueTypeSliceInt64:
+ cValues[vn].Value = append(cValues[vn].Value.(serialize.SliceInt64),
+ vv.Value.(serialize.SliceInt64)...)
+ default:
+ logrus.Errorf("AggregateValue unknown value type: %d", cv.ValueType)
+ }
+ }
+ }
+ }
+ for k, v := range cValues {
+ cmc.CreateValue(k, v.ValueType, v.Action)
+ cmc.SetValue(k, v.Value)
+ }
+}
+
+type WorkerComputer interface {
+ MakeContext(params map[string]string) *CWContext
+ Init() error
+ Scatters() []int
+ ScatterValue(sIdx int, vIdx int) serialize.MarshalAble
+ VertexValue(vertexId uint32) serialize.MarshalAble
+ BeforeStep()
+ Compute(vertexId uint32, pId int)
+ AfterStep()
+ Close()
+ Output() []byte
+ Context() *CWContext
+ OutputValueType() string
+}
+
+type MasterComputer interface {
+ MakeContext(params map[string]string) *CMContext
+ Init() error
+ BeforeStep()
+ Compute() bool
+ AfterStep()
+ Close()
+ Statistics() map[string]any
+ Output([][]byte) any
+ Context() *CMContext
+}
+
+type WorkerComputerBase struct {
+ WContext *CWContext
+}
+
+func (wcb *WorkerComputerBase) MakeContext(params map[string]string) *CWContext {
+ wcb.WContext = &CWContext{
+ CContext: CContext{
+ Context: context.Background(),
+ CValues: make(map[string]*CValue),
+ },
+ Params: params,
+ }
+ return wcb.WContext
+}
+
+func (wcb *WorkerComputerBase) Context() *CWContext {
+ return wcb.WContext
+}
+
+func (wcb *WorkerComputerBase) CreateVertexValue() VValues {
+ return VValues{}
+}
+
+func (wcb *WorkerComputerBase) Init() error {
+ return nil
+}
+func (wcb *WorkerComputerBase) Scatters() []int {
+ return []int{}
+}
+func (wcb *WorkerComputerBase) ScatterValue(int, int) serialize.MarshalAble {
+ return nil
+}
+func (wcb *WorkerComputerBase) BeforeStep() {
+}
+func (wcb *WorkerComputerBase) Compute(vertexID uint32, pID int) {
+ _, _ = vertexID, pID
+}
+func (wcb *WorkerComputerBase) AfterStep() {
+}
+func (wcb *WorkerComputerBase) VertexValue(vertexId uint32) serialize.MarshalAble {
+ _ = vertexId
+ return nil
+}
+func (wcb *WorkerComputerBase) Close() {
+}
+func (wcb *WorkerComputerBase) Output() []byte {
+ return nil
+}
+func (wcb *WorkerComputerBase) OutputValueType() string {
+ return "TEXT"
+}
+
+type MasterComputerBase struct {
+ MContext *CMContext
+}
+
+func (mcb *MasterComputerBase) MakeContext(params map[string]string) *CMContext {
+ mcb.MContext = &CMContext{
+ CContext: CContext{
+ Context: context.Background(),
+ CValues: make(map[string]*CValue),
+ },
+ Params: params,
+ WorkerCValues: make(map[string]map[string]*CValue),
+ }
+ return mcb.MContext
+}
+
+func (mcb *MasterComputerBase) Init() error {
+ return nil
+}
+func (mcb *MasterComputerBase) BeforeStep() {
+}
+func (mcb *MasterComputerBase) Compute() bool {
+ return true
+}
+func (mcb *MasterComputerBase) AfterStep() {
+}
+func (mcb *MasterComputerBase) Close() {
+}
+func (mcb *MasterComputerBase) Output([][]byte) any {
+ return nil
+}
+func (mcb *MasterComputerBase) Statistics() map[string]any {
+ return nil
+}
+func (mcb *MasterComputerBase) Context() *CMContext {
+ return mcb.MContext
+}
+
+type AlgorithmMaker interface {
+ CreateWorkerComputer() WorkerComputer
+ CreateMasterComputer() MasterComputer
+ Name() string
+ // DataNeeded outEdge, outDegree, undirected
+ DataNeeded() []string
+ SupportedStatistics() map[StatisticsType]struct{}
+ Type() AlgorithmType
+}
+type AlgorithmMakerBase struct {
+}
+
+func (a *AlgorithmMakerBase) CreateWorkerComputer() WorkerComputer {
+ return &WorkerComputerBase{}
+}
+func (a *AlgorithmMakerBase) CreateMasterComputer() MasterComputer {
+ return &MasterComputerBase{}
+}
+func (a *AlgorithmMakerBase) Name() string {
+ return "base"
+}
+func (a *AlgorithmMakerBase) DataNeeded() []string {
+ return []string{}
+}
+func (a *AlgorithmMakerBase) SupportedStatistics() map[StatisticsType]struct{} {
+ return map[StatisticsType]struct{}{StatisticsTypeIgnore: {}}
+}
+func (a *AlgorithmMakerBase) Type() AlgorithmType {
+ return AlgorithmOLAP
+}
+
+var AlgorithmManager = &algorithmManager{}
+
+type algorithmManager struct {
+ algorithms map[string]AlgorithmMaker
+}
+
+func (am *algorithmManager) Init() {
+ am.algorithms = make(map[string]AlgorithmMaker)
+}
+
+func (am *algorithmManager) Register(maker AlgorithmMaker, from string) {
+ am.algorithms[maker.Name()] = maker
+ logrus.Infof("Algorithm register OK, %s, from: %s", maker.Name(), from)
+}
+
+func (am *algorithmManager) GetMaker(algoName string) AlgorithmMaker {
+ if maker, ok := am.algorithms[algoName]; ok {
+ return maker
+ }
+ logrus.Errorf("GetMaker error, algo not exists: %s", algoName)
+ return nil
+}
+
+func (am *algorithmManager) MakeWorkerComputer(algoName string) WorkerComputer {
+ if maker, ok := am.algorithms[algoName]; ok {
+ return maker.CreateWorkerComputer()
+ }
+ logrus.Errorf("MakeWorkerComputer error, algo not exists: %s", algoName)
+ return nil
+}
+
+func (am *algorithmManager) MakeMasterComputer(algoName string) (MasterComputer, error) {
+ if maker, ok := am.algorithms[algoName]; ok {
+ return maker.CreateMasterComputer(), nil
+ }
+ logrus.Errorf("MakeMasterComputer error, algo not exists: %s", algoName)
+ return nil, fmt.Errorf("MakeMasterComputer error, algo not exists: %s", algoName)
+}
+
+func (am *algorithmManager) LoadPlugins() {
+ projectPath, _ := filepath.Abs(filepath.Dir(os.Args[0]))
+ pluginPath := filepath.Join(projectPath, "plugins/")
+ _ = filepath.Walk(pluginPath, func(path string, info fs.FileInfo, err error) error {
+ if info == nil {
+ return nil
+ }
+ if !strings.HasSuffix(info.Name(), ".so") {
+ return nil
+ }
+
+ plug, err := plugin.Open(path)
+ if err != nil {
+ logrus.Warnf("open plugin error: %s, %s", path, err)
+ return nil
+ }
+
+ makerPlug, err := plug.Lookup("AlgoMaker")
+ if err != nil {
+ logrus.Warnf("plugin lookup error: %s, %s", path, err)
+ return nil
+ }
+
+ maker, ok := makerPlug.(AlgorithmMaker)
+ if !ok {
+ logrus.Warnf("unexpected type from module symbol")
+ }
+
+ am.Register(maker, "plugin")
+
+ return nil
+ })
+}
diff --git a/vermeer/apps/compute/filter.go b/vermeer/apps/compute/filter.go
new file mode 100644
index 00000000..5bbe74ae
--- /dev/null
+++ b/vermeer/apps/compute/filter.go
@@ -0,0 +1,180 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package compute
+
+import (
+ "fmt"
+ "vermeer/apps/structure"
+
+ "github.com/sirupsen/logrus"
+
+ "github.com/antonmedv/expr"
+ "github.com/antonmedv/expr/vm"
+)
+
+type VertexFilter struct {
+ filter *Filter
+ properties structure.VertexPropertyInterface
+}
+
+func (v *VertexFilter) Init(exprStr string, propertyKeys []string, properties structure.VertexPropertyInterface) error {
+ v.properties = properties
+ initEnv := make(map[string]any, len(propertyKeys))
+ for _, key := range propertyKeys {
+ valueType, ok := v.properties.GetValueType(key)
+ if !ok {
+ logrus.Errorf("property %v not found in vertex", key)
+ continue
+ }
+ switch valueType {
+ case structure.ValueTypeInt32:
+ initEnv[key] = 0
+ case structure.ValueTypeFloat32:
+ initEnv[key] = float32(0)
+ case structure.ValueTypeString:
+ initEnv[key] = ""
+ }
+ }
+ v.filter = &Filter{}
+ err := v.filter.Init(exprStr, initEnv)
+ if err != nil {
+ return err
+ }
+ _, err = v.filter.Do()
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (v *VertexFilter) Filter(vertexID uint32) bool {
+ for key := range v.filter.env {
+ var value any
+ valueType, ok := v.properties.GetValueType(key)
+ if !ok {
+ logrus.Errorf("property %v not found in vertex", key)
+ continue
+ }
+ switch valueType {
+ case structure.ValueTypeInt32:
+ value = int(v.properties.GetInt32Value(key, vertexID))
+ case structure.ValueTypeFloat32:
+ value = float32(v.properties.GetFloat32Value(key, vertexID))
+ case structure.ValueTypeString:
+ value = string(v.properties.GetStringValue(key, vertexID))
+ }
+ v.filter.env[key] = value
+ }
+
+ do, err := v.filter.Do()
+ if err != nil {
+ logrus.Errorf("fliter error:%v", err)
+ return false
+ }
+ return do
+}
+
+type EdgeFilter struct {
+ filter *Filter
+ properties structure.EdgesPropertyInterface
+}
+
+func (e *EdgeFilter) Init(exprStr string, propertyKeys []string, properties structure.EdgesPropertyInterface) error {
+ e.properties = properties
+ initEnv := make(map[string]any, len(propertyKeys))
+ for _, key := range propertyKeys {
+ valueType, ok := e.properties.GetValueType(key)
+ if !ok {
+ logrus.Errorf("property %v not found in edge", key)
+ continue
+ }
+ switch valueType {
+ case structure.ValueTypeInt32:
+ initEnv[key] = 0
+ case structure.ValueTypeFloat32:
+ initEnv[key] = float32(0)
+ case structure.ValueTypeString:
+ initEnv[key] = ""
+ }
+ }
+ e.filter = &Filter{}
+ err := e.filter.Init(exprStr, initEnv)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (e *EdgeFilter) Filter(vertID uint32, idx uint32) bool {
+ for key := range e.filter.env {
+ var value any
+ valueType, ok := e.properties.GetValueType(key)
+ if !ok {
+ logrus.Errorf("property %v not found in edge", key)
+ continue
+ }
+ switch valueType {
+ case structure.ValueTypeInt32:
+ value = int(e.properties.GetInt32Value(key, vertID, idx))
+ case structure.ValueTypeFloat32:
+ value = float32(e.properties.GetFloat32Value(key, vertID, idx))
+ case structure.ValueTypeString:
+ value = string(e.properties.GetStringValue(key, vertID, idx))
+ }
+ //value, err := e.properties.GetValues(key, vertID, idx)
+ //if err != nil {
+ // logrus.Errorf("filter get values error:%v", err)
+ // return false
+ //}
+ e.filter.env[key] = value
+ }
+
+ do, err := e.filter.Do()
+ if err != nil {
+ logrus.Errorf("fliter error:%v", err)
+ return false
+ }
+ return do
+}
+
+type Filter struct {
+ program *vm.Program
+ env map[string]any
+}
+
+func (f *Filter) Init(exprStr string, initEnv map[string]any) error {
+ f.env = initEnv
+ var err error
+ f.program, err = expr.Compile(exprStr, expr.Env(initEnv))
+ if err != nil {
+ return fmt.Errorf("filter expr compile error:%w", err)
+ }
+ return nil
+}
+
+func (f *Filter) Do() (bool, error) {
+ run, err := expr.Run(f.program, f.env)
+ if err != nil {
+ return false, err
+ }
+ res, ok := run.(bool)
+ if !ok {
+ return false, fmt.Errorf("filter result not bool:%v", run)
+ }
+ return res, nil
+}
diff --git a/vermeer/apps/compute/statistics.go b/vermeer/apps/compute/statistics.go
new file mode 100644
index 00000000..87a00aa8
--- /dev/null
+++ b/vermeer/apps/compute/statistics.go
@@ -0,0 +1,615 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package compute
+
+import (
+ "encoding/json"
+ "math"
+ "time"
+ "vermeer/apps/serialize"
+ "vermeer/apps/structure"
+
+ "github.com/sirupsen/logrus"
+)
+
+type StatisticsType string
+
+const (
+ StatisticsTypeCount StatisticsType = "count"
+ StatisticsTypeModularity StatisticsType = "modularity"
+ StatisticsTypeTopK StatisticsType = "top_k"
+ StatisticsTypeIgnore StatisticsType = "ignore"
+ // sketch statistics
+ StatisticsTypeSketchDegree StatisticsType = "sketch_degree"
+ StatisticsTypeSketchDepth StatisticsType = "sketch_depth"
+ StatisticsTypeSketchCount StatisticsType = "sketch_count"
+)
+
+func StatisticsWorkerMaker(statisticsType StatisticsType) StatisticsWorker {
+ switch statisticsType {
+ case StatisticsTypeIgnore:
+ return &StatisticsWorkerBase{}
+ case StatisticsTypeCount:
+ return &StatisticsCountWorker{}
+ case StatisticsTypeModularity:
+ return &StatisticsModularityWorker{}
+ case StatisticsTypeTopK:
+ return &StatisticsTopKWorker{}
+ case StatisticsTypeSketchDegree:
+ return &StatisticsSketchDegreeWorker{}
+ case StatisticsTypeSketchDepth:
+ return &StatisticsSketchDepthWorker{}
+ case StatisticsTypeSketchCount:
+ return &StatisticsSketchCountWorker{}
+ }
+ return nil
+}
+
+func StatisticsMasterMaker(statisticsType StatisticsType) StatisticsMaster {
+ switch statisticsType {
+ case StatisticsTypeIgnore:
+ return &StatisticsMasterBase{}
+ case StatisticsTypeCount:
+ return &StatisticsCountMaster{}
+ case StatisticsTypeModularity:
+ return &StatisticsModularityMaster{}
+ case StatisticsTypeTopK:
+ return &StatisticsTopKMaster{}
+ case StatisticsTypeSketchDegree:
+ return &StatisticsSketchDegreeMaster{}
+ case StatisticsTypeSketchDepth:
+ return &StatisticsSketchDepthMaster{}
+ case StatisticsTypeSketchCount:
+ return &StatisticsSketchCountMaster{}
+ }
+ return nil
+}
+
+type StatisticsWorker interface {
+ Init(params map[string]string)
+ Collect(vertexID uint32, value serialize.MarshalAble)
+ NeedCollectAll() bool
+ CollectAll(vertexID uint32, value serialize.MarshalAble)
+ Output() []byte
+ MakeContext() *SWContext
+}
+
+type StatisticsMaster interface {
+ Init(params map[string]string)
+ Aggregate(data []byte)
+ Output() map[string]any
+ MakeContext() *SMContext
+}
+
+type SWContext struct {
+ Graph *structure.GraphData
+}
+
+type StatisticsWorkerBase struct {
+ sContext *SWContext
+}
+
+func (b *StatisticsWorkerBase) Init(params map[string]string) {
+}
+func (b *StatisticsWorkerBase) Collect(vertexID uint32, value serialize.MarshalAble) {
+}
+func (b *StatisticsWorkerBase) CollectAll(vertexID uint32, value serialize.MarshalAble) {
+}
+func (b *StatisticsWorkerBase) NeedCollectAll() bool {
+ return false
+}
+func (b *StatisticsWorkerBase) Output() []byte {
+ return nil
+}
+func (b *StatisticsWorkerBase) MakeContext() *SWContext {
+ b.sContext = &SWContext{}
+ return b.sContext
+}
+
+type SMContext struct {
+ Graph *structure.VermeerGraph
+}
+type StatisticsMasterBase struct {
+ sContext *SMContext
+}
+
+func (b *StatisticsMasterBase) Init(params map[string]string) {
+}
+func (b *StatisticsMasterBase) Aggregate(data []byte) {
+}
+func (b *StatisticsMasterBase) Output() map[string]any {
+ return nil
+}
+func (b *StatisticsMasterBase) MakeContext() *SMContext {
+ b.sContext = &SMContext{}
+ return b.sContext
+}
+
+type StatisticsCountWorker struct {
+ StatisticsWorkerBase
+ count map[string]struct{}
+}
+
+func (b *StatisticsCountWorker) Init(params map[string]string) {
+ _ = params
+ b.count = make(map[string]struct{})
+}
+
+func (b *StatisticsCountWorker) Collect(vertexID uint32, value serialize.MarshalAble) {
+ _ = vertexID
+ b.count[value.ToString()] = struct{}{}
+}
+
+func (b *StatisticsCountWorker) Output() []byte {
+ bytes, err := json.Marshal((*b).count)
+ if err != nil {
+ logrus.Errorf("json marshal error:%v", err)
+ return nil
+ }
+ return bytes
+}
+
+type StatisticsCountMaster struct {
+ StatisticsMasterBase
+ count map[string]struct{}
+}
+
+func (b *StatisticsCountMaster) Init(params map[string]string) {
+ _ = params
+ b.count = make(map[string]struct{})
+}
+
+func (b *StatisticsCountMaster) Aggregate(data []byte) {
+ d := make(map[string]struct{})
+ err := json.Unmarshal(data, &d)
+ if err != nil {
+ logrus.Errorf("json unmarshal error:%v", err)
+ return
+ }
+ for key := range d {
+ b.count[key] = struct{}{}
+ }
+}
+
+func (b *StatisticsCountMaster) Output() map[string]any {
+ return map[string]any{"count": len(b.count)}
+}
+
+// todo
+
+type community struct {
+ //社区的总度数
+ SigmaTot int64 `json:"sigma_tot"`
+ //本社区内部的连接度权重之和
+ KIn int64 `json:"k_in"`
+}
+
+type StatisticsModularityWorker struct {
+ StatisticsWorkerBase
+ communities map[string]*community
+ totalResult []string
+}
+
+func (b *StatisticsModularityWorker) Init(params map[string]string) {
+ b.communities = make(map[string]*community)
+ b.totalResult = make([]string, b.sContext.Graph.Vertex.TotalVertexCount())
+}
+
+func (b *StatisticsModularityWorker) Collect(vertexID uint32, value serialize.MarshalAble) {
+ vID := vertexID - b.sContext.Graph.VertIDStart
+ if b.communities[value.ToString()] == nil {
+ b.communities[value.ToString()] = &community{}
+ }
+
+ var count int64
+ //if b.sContext.Graph.BothEdges != nil {
+ // count = int64(len(b.sContext.Graph.InEdges[vID]) + len(b.sContext.Graph.OutEdges[vID]))
+ //} else
+ if b.sContext.Graph.Edges.UseOutEdges() {
+ count = int64(len(b.sContext.Graph.Edges.GetInEdges(vID)) + len(b.sContext.Graph.Edges.GetOutEdges(vID)))
+ }
+ b.communities[value.ToString()].SigmaTot += count
+}
+
+func (b *StatisticsModularityWorker) NeedCollectAll() bool {
+ return true
+}
+func (b *StatisticsModularityWorker) CollectAll(vertexID uint32, value serialize.MarshalAble) {
+ b.totalResult[vertexID] = value.ToString()
+}
+
+func (b *StatisticsModularityWorker) findSameComm(commID string, edges ...[]serialize.SUint32) int64 {
+ var KIn int64
+ for _, edge := range edges {
+ for _, edgeID := range edge {
+ if b.totalResult[edgeID] == commID {
+ KIn++
+ }
+ }
+ }
+ return KIn
+}
+
+func (b *StatisticsModularityWorker) Output() []byte {
+ for i := uint32(0); i < b.sContext.Graph.VertexCount; i++ {
+ longVertexID := i + b.sContext.Graph.VertIDStart
+ var KIn int64
+ //if b.sContext.Graph.BothEdges != nil {
+ // KIn = b.findSameComm(b.totalResult[longVertexID], b.sContext.Graph.BothEdges[i])
+ //} else
+ if b.sContext.Graph.Edges.UseOutEdges() {
+ KIn = b.findSameComm(b.totalResult[longVertexID], b.sContext.Graph.Edges.GetInEdges(i), b.sContext.Graph.Edges.GetOutEdges(i))
+ }
+ if b.communities[b.totalResult[longVertexID]] == nil {
+ b.communities[b.totalResult[longVertexID]] = &community{}
+ }
+ b.communities[b.totalResult[longVertexID]].KIn += KIn
+ }
+ bytes, err := json.Marshal(b.communities)
+ if err != nil {
+ logrus.Errorf("json marshal error:%v", err)
+ return nil
+ }
+ //logrus.Infof("%s", bytes)
+ return bytes
+}
+
+type StatisticsModularityMaster struct {
+ StatisticsMasterBase
+ communities map[string]*community
+}
+
+func (b *StatisticsModularityMaster) Init(params map[string]string) {
+ _ = params
+ b.communities = make(map[string]*community)
+}
+
+func (b *StatisticsModularityMaster) Aggregate(data []byte) {
+ //logrus.Infof("%s", data)
+ comm := make(map[string]community)
+ err := json.Unmarshal(data, &comm)
+ if err != nil {
+ logrus.Errorf("json unmarshal error:%v", err)
+ return
+ }
+ for s, c := range comm {
+ if b.communities[s] == nil {
+ b.communities[s] = &community{}
+ }
+ b.communities[s].SigmaTot += c.SigmaTot
+ b.communities[s].KIn += c.KIn
+ }
+}
+
+func (b *StatisticsModularityMaster) Output() map[string]any {
+ var modularity float64
+ edgeNums := 2 * b.sContext.Graph.EdgeCount
+ for _, c := range b.communities {
+ modularity += float64(c.KIn)/float64(edgeNums) - float64(c.SigmaTot)/float64(edgeNums)*float64(c.SigmaTot)/float64(edgeNums)
+ }
+ return map[string]any{"modularity": modularity}
+}
+
+// todo: not implemented
+type direction string
+
+const (
+ Asc direction = "asc"
+ Desc direction = "desc"
+)
+
+type StatisticsTopKWorker struct {
+ StatisticsWorkerBase
+}
+
+type StatisticsTopKMaster struct {
+ StatisticsMasterBase
+}
+
+type StatisticsSketchDegreeWorker struct {
+ StatisticsWorkerBase
+ degrees map[int32]int64
+ superVertex map[string]int32
+}
+
+func (sd *StatisticsSketchDegreeWorker) Init(params map[string]string) {
+ _ = params
+ sd.degrees = make(map[int32]int64, 100)
+ sd.superVertex = make(map[string]int32, 100)
+}
+func (sd *StatisticsSketchDegreeWorker) Collect(vertexID uint32, value serialize.MarshalAble) {
+ v, ok := value.(*serialize.SUint32)
+ if !ok {
+ logrus.Errorf("value is not serialize.SUint32")
+ }
+ vs := *v
+ sd.degrees[int32(vs)]++
+ if float64(vs) > float64(sd.sContext.Graph.EdgeCount)*0.01 {
+ sd.superVertex[sd.sContext.Graph.Vertex.GetVertex(vertexID).ID] = int32(vs)
+ }
+}
+
+func (sd *StatisticsSketchDegreeWorker) Output() []byte {
+ result := make([][]byte, 2)
+ degreeBytes, err := json.Marshal(sd.degrees)
+ if err != nil {
+ logrus.Errorf("json marshal error:%v", err)
+ return nil
+ }
+ result[0] = degreeBytes
+ superVertexBytes, err := json.Marshal(sd.superVertex)
+ if err != nil {
+ logrus.Errorf("json marshal error:%v", err)
+ return nil
+ }
+ result[1] = superVertexBytes
+
+ bytes, err := json.Marshal(result)
+ if err != nil {
+ logrus.Errorf("json marshal error:%v", err)
+ return nil
+ }
+ return bytes
+}
+
+type StatisticsSketchDegreeMaster struct {
+ StatisticsMasterBase
+ degreeAvg float64
+ dispersions map[int32]float64
+ minDegree int32
+ maxDegree int32
+ degrees map[int32]int64
+ afsUri string
+ afsFilePath string
+ superVertex []map[string]int32
+ edgeCount int64
+}
+
+func (sd *StatisticsSketchDegreeMaster) Init(params map[string]string) {
+ sd.afsUri = params["output.afs_uri"]
+ sd.afsFilePath = params["output.file_path"]
+ sd.minDegree = math.MaxInt32
+ sd.maxDegree = math.MinInt32
+ sd.degrees = make(map[int32]int64)
+ sd.dispersions = make(map[int32]float64)
+ sd.degreeAvg += float64(sd.sContext.Graph.EdgeCount) / float64(sd.sContext.Graph.VertexCount)
+ sd.edgeCount = sd.sContext.Graph.EdgeCount
+ sd.superVertex = make([]map[string]int32, 4)
+ for i := range sd.superVertex {
+ sd.superVertex[i] = make(map[string]int32)
+ }
+}
+func (sd *StatisticsSketchDegreeMaster) Aggregate(data []byte) {
+ result := make([][]byte, 2)
+ err := json.Unmarshal(data, &result)
+ if err != nil {
+ logrus.Errorf("json unmarshal error:%v", err)
+ }
+ superVertex := make(map[string]int32)
+ degrees := make(map[int32]int64)
+ err = json.Unmarshal(result[0], °rees)
+ if err != nil {
+ logrus.Errorf("json unmarshal error:%v", err)
+ }
+ err = json.Unmarshal(result[1], &superVertex)
+ if err != nil {
+ logrus.Errorf("json unmarshal error:%v", err)
+ }
+
+ for k, v := range degrees {
+ if k < sd.minDegree {
+ sd.minDegree = k
+ }
+ if k > sd.maxDegree {
+ sd.maxDegree = k
+ }
+ sd.degrees[k] += v
+ if _, ok := sd.dispersions[k]; !ok {
+ sd.dispersions[k] = (float64(v) - sd.degreeAvg) * (float64(v) - sd.degreeAvg)
+ }
+ }
+ for k, v := range superVertex {
+ if float64(v) >= float64(sd.edgeCount)*0.01 && float64(v) < float64(sd.edgeCount)*0.03 {
+ sd.superVertex[0][k] = v
+ } else if float64(v) >= float64(sd.edgeCount)*0.03 && float64(v) < float64(sd.edgeCount)*0.05 {
+ sd.superVertex[1][k] = v
+ } else if float64(v) >= float64(sd.edgeCount)*0.05 && float64(v) < float64(sd.edgeCount)*0.1 {
+ sd.superVertex[2][k] = v
+ } else if float64(v) >= float64(sd.edgeCount)*0.1 {
+ sd.superVertex[3][k] = v
+ }
+ }
+}
+
+func (sd *StatisticsSketchDegreeMaster) Output() map[string]any {
+ result := make(map[string]any)
+ result["afs_uri"] = sd.afsUri
+ result["afs_file"] = sd.afsFilePath
+ result["sketch_time"] = time.Now().Format(time.DateTime)
+ result["vertex_count"] = sd.sContext.Graph.VertexCount
+ result["edge_count"] = sd.sContext.Graph.EdgeCount
+ result["degree_avg"] = sd.degreeAvg
+ result["degree_min"] = sd.minDegree
+ result["degree_max"] = sd.maxDegree
+ result["isolated"] = sd.degrees[0]
+ degreePercent := (sd.maxDegree-sd.minDegree)/100 + 1
+ degrees := make([]int64, 100)
+ for k, v := range sd.degrees {
+ degrees[(k-sd.minDegree)/degreePercent] += v
+ }
+ result["degree"] = degrees
+ dispersion := float64(0)
+ for _, v := range sd.dispersions {
+ dispersion += v
+ }
+ result["dispersion"] = math.Sqrt(dispersion)
+
+ result["super_vertex"] = []map[string]int32{{}, {}, {}, {}}
+ result["super_vertex_count"] = []int{0, 0, 0, 0}
+ if sd.edgeCount > 100000 {
+ result["super_vertex"] = sd.superVertex
+ result["super_vertex_count"] = []int{len(sd.superVertex[0]), len(sd.superVertex[1]), len(sd.superVertex[2]), len(sd.superVertex[3])}
+ }
+
+ return result
+}
+
+type StatisticsSketchDepthWorker struct {
+ StatisticsWorkerBase
+ maxDepth serialize.SUint32
+}
+
+func (sd *StatisticsSketchDepthWorker) Init(params map[string]string) {
+ _ = params
+ sd.maxDepth = 0
+}
+
+func (sd *StatisticsSketchDepthWorker) Collect(vertexID uint32, value serialize.MarshalAble) {
+ _ = vertexID
+ v, ok := value.(*serialize.SUint32)
+ if !ok {
+ logrus.Errorf("value is not serialize.SUint32")
+ }
+ vs := *v
+ if vs > sd.maxDepth {
+ sd.maxDepth = vs
+ }
+}
+
+func (sd *StatisticsSketchDepthWorker) Output() []byte {
+ bytes := make([]byte, sd.maxDepth.PredictSize()+16)
+ _, err := sd.maxDepth.Marshal(bytes)
+ if err != nil {
+ logrus.Errorf("marshal error:%v", err)
+ }
+ return bytes
+}
+
+type StatisticsSketchDepthMaster struct {
+ StatisticsMasterBase
+ maxDepth serialize.SUint32
+ afsUri string
+ afsFilePath string
+}
+
+func (sd *StatisticsSketchDepthMaster) Init(params map[string]string) {
+ _ = params
+ sd.maxDepth = 0
+ sd.afsUri = params["output.afs_uri"]
+ sd.afsFilePath = params["output.file_path"]
+}
+
+func (sd *StatisticsSketchDepthMaster) Aggregate(data []byte) {
+ maxDepth := serialize.SUint32(0)
+ _, err := maxDepth.Unmarshal(data)
+ if err != nil {
+ logrus.Errorf("unmarshal error:%v", err)
+ }
+ if maxDepth > sd.maxDepth {
+ sd.maxDepth = maxDepth
+ }
+}
+
+func (sd *StatisticsSketchDepthMaster) Output() map[string]any {
+ return map[string]any{
+ "depth_max": sd.maxDepth,
+ "afs_uri": sd.afsUri,
+ "afs_file": sd.afsFilePath,
+ "sketch_time": time.Now().Format(time.DateTime),
+ }
+}
+
+type StatisticsSketchCountWorker struct {
+ StatisticsWorkerBase
+ count map[string]int64
+}
+
+func (sc *StatisticsSketchCountWorker) Init(params map[string]string) {
+ _ = params
+ sc.count = make(map[string]int64)
+}
+func (sc *StatisticsSketchCountWorker) Collect(vertexID uint32, value serialize.MarshalAble) {
+ _ = vertexID
+ sc.count[value.ToString()]++
+}
+
+func (sc *StatisticsSketchCountWorker) Output() []byte {
+ bytes, err := json.Marshal((*sc).count)
+ if err != nil {
+ logrus.Errorf("json marshal error:%v", err)
+ return nil
+ }
+ return bytes
+}
+
+type StatisticsSketchCountMaster struct {
+ StatisticsMasterBase
+ afsUri string
+ afsFilePath string
+ count map[string]int64
+}
+
+func (sc *StatisticsSketchCountMaster) Init(params map[string]string) {
+ sc.afsUri = params["output.afs_uri"]
+ sc.afsFilePath = params["output.file_path"]
+ sc.count = make(map[string]int64)
+}
+func (sc *StatisticsSketchCountMaster) Aggregate(data []byte) {
+ d := make(map[string]int64)
+ err := json.Unmarshal(data, &d)
+ if err != nil {
+ logrus.Errorf("json unmarshal error:%v", err)
+ return
+ }
+ for key, count := range d {
+ sc.count[key] += count
+ }
+}
+
+func (sc *StatisticsSketchCountMaster) Output() map[string]any {
+ result := make(map[string]any)
+ result["afs_uri"] = sc.afsUri
+ result["afs_file"] = sc.afsFilePath
+ result["sketch_time"] = time.Now().Format(time.DateTime)
+ minCount := int64(math.MaxInt64)
+ maxCount := int64(math.MinInt64)
+ bigCommunity := 0
+ nodeCount := (sc.sContext.Graph.VertexCount / 100)
+ for _, v := range sc.count {
+ if minCount > v {
+ minCount = v
+ }
+ if maxCount < v {
+ maxCount = v
+ }
+ if v > nodeCount && v > 100 {
+ bigCommunity++
+ }
+ }
+ result["count"] = len(sc.count)
+ result["min_count"] = minCount
+ result["max_count"] = maxCount
+ communityPercent := (maxCount-minCount)/100 + 1
+ community := make([]int64, 100)
+ for _, v := range sc.count {
+ community[(v-minCount)/communityPercent] += v
+ }
+ result["community"] = community
+ result["big_community"] = bigCommunity
+ return result
+}
diff --git a/vermeer/apps/compute/task.go b/vermeer/apps/compute/task.go
new file mode 100644
index 00000000..b73cd80e
--- /dev/null
+++ b/vermeer/apps/compute/task.go
@@ -0,0 +1,199 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package compute
+
+import (
+ "fmt"
+ "io"
+ "sync"
+ "time"
+ "vermeer/apps/common"
+ "vermeer/apps/options"
+ "vermeer/apps/structure"
+
+ "github.com/sirupsen/logrus"
+)
+
+type ComputerTask struct {
+ Task *structure.TaskInfo
+ Algorithm string
+ Step int32
+ ComputeWorker WorkerComputer
+ ComputeMaster MasterComputer
+ ComputeValue ComputerResult
+ Statistics [][]byte
+ TpResult computeTpResult
+ Parallel *int32
+ StepWg *sync.WaitGroup
+ RecvWg *common.SpinWaiter
+ Locker *sync.Mutex
+ SettingOutDegree bool
+ SettingOutEdges bool
+ SendCount map[string]*int32
+ RecvCount map[string]*int32
+}
+
+func (ct *ComputerTask) FreeMemory() {
+ ct.ComputeMaster = nil
+ ct.ComputeWorker = nil
+ ct.SendCount = nil
+ ct.RecvCount = nil
+}
+
+type ComputerResult struct {
+ Values []VertexValue
+ Timer *time.Timer
+}
+
+type computeTpResult struct {
+ WorkerResult [][]byte
+ Output any
+}
+
+type VertexValue struct {
+ ID string
+ Value string
+}
+
+var ComputerTaskManager = &computerTaskManager{}
+
+type computerTaskManager struct {
+ tasks map[int32]*ComputerTask
+ locker sync.Mutex
+}
+
+// type ComputerTaskSpace struct {
+// tasks map[int32]*ComputerTask
+// locker sync.Mutex
+// }
+
+// func (ctm *computerTaskManager) AddSpace(spaceName string) error {
+// ctm.Lock()
+// defer ctm.Unlock()
+// if ctm.ComputerTaskSpaceMap[spaceName] != nil {
+// return fmt.Errorf("ComputerTaskManager space exists:%s", spaceName)
+// }
+// computerTaskSpace := &ComputerTaskSpace{}
+// computerTaskSpace.Init()
+// ctm.ComputerTaskSpaceMap[spaceName] = computerTaskSpace
+// return nil
+// }
+
+func (ctm *computerTaskManager) Init() {
+ ctm.tasks = make(map[int32]*ComputerTask)
+}
+
+// func (cts *ComputerTaskSpace) Init() {
+// cts.tasks = make(map[int32]*ComputerTask)
+// cts.locker = sync.Mutex{}
+// }
+
+func (ctm *computerTaskManager) MakeTask(taskInfo *structure.TaskInfo) *ComputerTask {
+ ctm.locker.Lock()
+ defer ctm.locker.Unlock()
+ algo := ctm.ExtractAlgo(taskInfo.Params)
+ ct := ComputerTask{
+ Task: taskInfo,
+ Algorithm: algo,
+ Step: 0,
+ Parallel: new(int32),
+ StepWg: &sync.WaitGroup{},
+ RecvWg: new(common.SpinWaiter),
+ Locker: &sync.Mutex{},
+ }
+ ctm.tasks[taskInfo.ID] = &ct
+ return &ct
+}
+
+func (ctm *computerTaskManager) ExtractAlgo(params map[string]string) string {
+ if params == nil {
+ return ""
+ }
+ return options.GetString(params, "compute.algorithm")
+}
+
+func (ctm *computerTaskManager) GetTask(taskID int32) *ComputerTask {
+ ctm.locker.Lock()
+ defer ctm.locker.Unlock()
+ return ctm.tasks[taskID]
+}
+
+func (ctm *computerTaskManager) GetComputeValues(taskID int32, cursor, limit int) ([]VertexValue, int32, error) {
+ ctm.locker.Lock()
+ defer ctm.locker.Unlock()
+ task := ctm.tasks[taskID]
+ if task == nil {
+ return nil, 0, fmt.Errorf("task_id error:%v", taskID)
+ }
+ if len(task.ComputeValue.Values) == 0 {
+ return nil, 0, fmt.Errorf("compute value not exist, task_id:%v", taskID)
+ }
+ if cursor == len(task.ComputeValue.Values) {
+ return nil, 0, io.EOF
+ } else if cursor < 0 || cursor > len(task.ComputeValue.Values) {
+ return nil, 0, fmt.Errorf("cursor:%v must be >= 0 and <= %v", cursor, len(task.ComputeValue.Values))
+ }
+ start := cursor
+ end := cursor + limit
+ if end > len(task.ComputeValue.Values) {
+ end = len(task.ComputeValue.Values)
+ }
+ computeValue := make([]VertexValue, end-start)
+ copy(computeValue, task.ComputeValue.Values[start:end])
+ //更新时间
+ if task.ComputeValue.Timer != nil {
+ task.ComputeValue.Timer.Stop()
+ }
+ ctm.initTimer(taskID)
+ return computeValue, int32(end), nil
+}
+
+func (ctm *computerTaskManager) DeleteTask(taskID int32) {
+ ctm.locker.Lock()
+ defer ctm.locker.Unlock()
+ ctm.deleteTask(taskID)
+}
+
+func (ctm *computerTaskManager) deleteTask(taskID int32) {
+ delete(ctm.tasks, taskID)
+}
+
+func (ctm *computerTaskManager) InitTimer(taskID int32) {
+ ctm.locker.Lock()
+ defer ctm.locker.Unlock()
+ ctm.initTimer(taskID)
+}
+
+func (ctm *computerTaskManager) initTimer(taskID int32) {
+ ct := ctm.tasks[taskID]
+ ct.ComputeValue.Timer = time.AfterFunc(10*time.Minute, func() {
+ ct.ComputeValue.Values = nil
+ ctm.DeleteTask(taskID)
+ logrus.Infof("computer result deleted, task_id:%v", taskID)
+ })
+}
+
+func (ctm *computerTaskManager) GetOltpResult(taskID int32) any {
+ ctm.locker.Lock()
+ defer ctm.locker.Unlock()
+ task := ctm.tasks[taskID]
+ if task == nil {
+ return nil
+ }
+ return task.TpResult.Output
+}
diff --git a/vermeer/apps/graphio/hdfs_file.go b/vermeer/apps/graphio/hdfs_file.go
new file mode 100644
index 00000000..298ba2f3
--- /dev/null
+++ b/vermeer/apps/graphio/hdfs_file.go
@@ -0,0 +1,371 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package graphio
+
+import (
+ "bufio"
+ "encoding/json"
+ "fmt"
+ "os/user"
+ "path/filepath"
+ "strings"
+ "vermeer/apps/common"
+ "vermeer/apps/options"
+ "vermeer/apps/structure"
+
+ "github.com/colinmarc/hdfs/v2"
+ "github.com/colinmarc/hdfs/v2/hadoopconf"
+ krb "github.com/jcmturner/gokrb5/v8/client"
+ krbCfg "github.com/jcmturner/gokrb5/v8/config"
+ krbKeytab "github.com/jcmturner/gokrb5/v8/keytab"
+ "github.com/sirupsen/logrus"
+)
+
+const useKrbYes = 1
+
+func init() {
+ LoadMakers[LoadTypeHdfs] = &HdfsMaker{}
+}
+
+type HdfsMaker struct{}
+
+func (a *HdfsMaker) CreateGraphLoader() GraphLoader {
+ return &HdfsLoader{}
+}
+
+func (a *HdfsMaker) CreateGraphWriter() GraphWriter {
+ return &HdfsWriter{}
+}
+
+func (a *HdfsMaker) MakeTasks(params map[string]string, taskID int32) ([]LoadPartition, error) {
+ partID := int32(1)
+ loadParts := make([]LoadPartition, 0)
+
+ namenode := options.GetString(params, "load.hdfs_namenode")
+ hdfsConfPath := options.GetString(params, "load.hdfs_conf_path")
+ krbRealm := options.GetString(params, "load.krb_realm")
+ krbName := options.GetString(params, "load.krb_name")
+ keytabPath := options.GetString(params, "load.krb_keytab_path")
+ krbConfPath := options.GetString(params, "load.krb_conf_path")
+ useKrb := options.GetInt(params, "load.hdfs_use_krb")
+ client, err := GetHdfsClient(namenode, hdfsConfPath, krbRealm, keytabPath, krbConfPath, krbName, useKrb)
+ if err != nil {
+ return nil, err
+ }
+ files := options.GetString(params, "load.vertex_files")
+
+ dir := filepath.Dir(files)
+ readDir, err := client.ReadDir(dir)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, info := range readDir {
+ newPath := filepath.Join(dir, info.Name())
+ match, err := filepath.Match(files, newPath)
+ if err != nil {
+ return nil, err
+ }
+ if match {
+ part := LoadPartition{}
+ part.Init(partID, taskID, LoadPartTypeVertex)
+ part.Params = make(map[string]string)
+ part.Params["load.file_path"] = newPath
+ loadParts = append(loadParts, part)
+ partID += 1
+ }
+ }
+
+ files = options.GetString(params, "load.edge_files")
+ dir = filepath.Dir(files)
+ readDir, err = client.ReadDir(dir)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, info := range readDir {
+ newPath := filepath.Join(dir, info.Name())
+ match, err := filepath.Match(files, newPath)
+ if err != nil {
+ return nil, err
+ }
+ if match {
+ part := LoadPartition{}
+ part.Init(partID, taskID, LoadPartTypeEdge)
+ part.Params = make(map[string]string)
+ part.Params["load.file_path"] = newPath
+ loadParts = append(loadParts, part)
+ partID += 1
+ }
+ }
+
+ for i := range loadParts {
+ loadParts[i].Params["load.hdfs_namenode"] = params["load.hdfs_namenode"]
+ loadParts[i].Params["load.hdfs_conf_path"] = params["load.hdfs_conf_path"]
+ loadParts[i].Params["load.krb_realm"] = params["load.krb_realm"]
+ loadParts[i].Params["load.krb_name"] = params["load.krb_name"]
+ loadParts[i].Params["load.krb_keytab_path"] = params["load.krb_keytab_path"]
+ loadParts[i].Params["load.krb_conf_path"] = params["load.krb_conf_path"]
+ loadParts[i].Params["load.hdfs_use_krb"] = params["load.hdfs_use_krb"]
+ }
+ return loadParts, nil
+}
+
+type HdfsLoader struct {
+ reader *bufio.Reader
+ file *hdfs.FileReader
+ client *hdfs.Client
+ schema structure.PropertySchema
+ filename string
+ delimiter string
+ count int
+ useProperty bool
+}
+
+func (hl *HdfsLoader) Init(params map[string]string, schema structure.PropertySchema) error {
+ hl.useProperty = false
+ if options.GetInt(params, "load.use_property") == 1 {
+ hl.useProperty = true
+ hl.schema = schema
+ }
+ namenode := options.GetString(params, "load.hdfs_namenode")
+ hdfsConfPath := options.GetString(params, "load.hdfs_conf_path")
+ krbRealm := options.GetString(params, "load.krb_realm")
+ krbName := options.GetString(params, "load.krb_name")
+ keytabPath := options.GetString(params, "load.krb_keytab_path")
+ krbConfPath := options.GetString(params, "load.krb_conf_path")
+ useKrb := options.GetInt(params, "load.hdfs_use_krb")
+ client, err := GetHdfsClient(namenode, hdfsConfPath, krbRealm, keytabPath, krbConfPath, krbName, useKrb)
+ if err != nil {
+ return err
+ }
+ hl.client = client
+ hl.filename = options.GetString(params, "load.file_path")
+ hl.file, err = hl.client.Open(hl.filename)
+ if err != nil {
+ return err
+ }
+ hl.reader = bufio.NewReader(hl.file)
+ hl.delimiter = options.GetString(params, "load.delimiter")
+ return nil
+}
+
+func (hl *HdfsLoader) ReadVertex(vertex *structure.Vertex, property *structure.PropertyValue) error {
+ line, err := hl.reader.ReadString('\n')
+ if err != nil {
+ return err
+ }
+ line = strings.TrimSpace(line)
+ ss := strings.Split(line, hl.delimiter)
+ vertex.ID = ss[0]
+ if hl.useProperty && len(ss) > 1 {
+ propStr := strings.TrimSpace(line[len(ss[0]):])
+ property.LoadFromString(propStr, hl.schema)
+ }
+ hl.count += 1
+ return nil
+}
+
+func (hl *HdfsLoader) ReadEdge(edge *structure.Edge, property *structure.PropertyValue) error {
+ line, err := hl.reader.ReadString('\n')
+ if err != nil {
+ return err
+ }
+ line = strings.TrimSpace(line)
+ ss := strings.Split(line, hl.delimiter)
+ if len(ss) < 2 {
+ return fmt.Errorf("read edge format error")
+ }
+ edge.Source = ss[0]
+ edge.Target = ss[1]
+ if hl.useProperty && len(ss) > 2 {
+ var ps string
+ for i := 2; i < len(ss); i++ {
+ ps += ss[i]
+ }
+ propStr := strings.TrimSpace(ps)
+ property.LoadFromString(propStr, hl.schema)
+ }
+ hl.count += 1
+ return nil
+}
+func (hl *HdfsLoader) Name() string {
+ return hl.filename
+}
+func (hl *HdfsLoader) ReadCount() int {
+ return hl.count
+}
+
+func (hl *HdfsLoader) Close() {
+ err := hl.file.Close()
+ if err != nil {
+ logrus.Errorf("hdfs loader file close error: %s", err)
+ }
+ err = hl.client.Close()
+ if err != nil {
+ logrus.Errorf("hdfs loader client close error: %s", err)
+ }
+}
+
+type HdfsWriter struct {
+ file *hdfs.FileWriter
+ writer *bufio.Writer
+ client *hdfs.Client
+ count int
+ delimiter string
+ filePath string
+}
+
+func (hw *HdfsWriter) Init(info WriterInitInfo) error {
+ hw.delimiter = options.GetString(info.Params, "output.delimiter")
+ switch info.Mode {
+ case WriteModeVertexValue:
+ hw.filePath = options.GetString(info.Params, "output.file_path")
+ zeroPad := 1
+ for info.MaxID /= 10; info.MaxID > 0; info.MaxID /= 10 {
+ zeroPad++
+ }
+ hw.filePath += "_" + common.ItoaPad(info.PartID, zeroPad)
+ case WriteModeStatistics:
+ hw.filePath = options.GetString(info.Params, "output.statistics_file_path")
+ }
+ namenode := options.GetString(info.Params, "output.hdfs_namenode")
+ hdfsConfPath := options.GetString(info.Params, "output.hdfs_conf_path")
+ krbRealm := options.GetString(info.Params, "output.krb_realm")
+ krbName := options.GetString(info.Params, "output.krb_name")
+ keytabPath := options.GetString(info.Params, "output.krb_keytab_path")
+ krbConfPath := options.GetString(info.Params, "output.krb_conf_path")
+ useKrb := options.GetInt(info.Params, "output.hdfs_use_krb")
+ client, err := GetHdfsClient(namenode, hdfsConfPath, krbRealm, keytabPath, krbConfPath, krbName, useKrb)
+ if err != nil {
+ return err
+ }
+ hw.client = client
+ //find dir path
+ dirPath := filepath.Dir(hw.filePath)
+ err = hw.client.MkdirAll(dirPath, 0755)
+ if err != nil {
+ logrus.Errorf("hdfs writer mkdir error: %s", err)
+ return err
+ }
+ //清空已有同名文件实现覆盖写
+ _ = hw.client.Remove(hw.filePath)
+ hw.file, err = hw.client.CreateFile(hw.filePath, 3, 128*1024*1024, 0666)
+ if err != nil {
+ logrus.Errorf("hdfs writer open file error: %s", err)
+ return err
+ }
+ hw.writer = bufio.NewWriterSize(hw.file, 1*1024*1024)
+ return nil
+}
+
+func (hw *HdfsWriter) WriteVertex(vertexValue WriteVertexValue) {
+ builder := strings.Builder{}
+ valueString := vertexValue.Value.ToString()
+ builder.Grow(len(vertexValue.VertexID) + len(valueString) + 2)
+ builder.WriteString(vertexValue.VertexID)
+ builder.WriteString(hw.delimiter)
+ builder.WriteString(valueString)
+ builder.WriteString("\n")
+ _, _ = hw.writer.WriteString(builder.String())
+}
+
+func (hw *HdfsWriter) WriteCount() int {
+ return hw.count
+}
+
+func (hw *HdfsWriter) WriteStatistics(statistics map[string]any) error {
+ bytes, err := json.Marshal(statistics)
+ if err != nil {
+ return err
+ }
+ _, err = hw.writer.Write(bytes)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (hw *HdfsWriter) Close() {
+ err := hw.writer.Flush()
+ if err != nil {
+ logrus.Errorf("hdfs writer flush error: %s", err)
+ }
+ err = hw.file.Close()
+ if err != nil {
+ logrus.Errorf("hdfs writer file close error: %s", err)
+ }
+ err = hw.client.Close()
+ if err != nil {
+ logrus.Errorf("hdfs writer client close error: %s", err)
+ }
+}
+
+func GetHdfsClient(namenode, hdfsConfPath, realm, keytabPath, krbConfPath, krbName string, useKrb int) (*hdfs.Client, error) {
+ conf, err := hadoopconf.LoadFromEnvironment()
+ if err != nil {
+ return nil, err
+ }
+
+ if hdfsConfPath != "" {
+ conf, err = hadoopconf.Load(hdfsConfPath)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ option := hdfs.ClientOptionsFromConf(conf)
+ if namenode != "" {
+ option.Addresses = strings.Split(namenode, ",")
+ }
+
+ u, err := user.Current()
+ if err != nil {
+ return nil, err
+ }
+ option.User = u.Username
+ if useKrb == useKrbYes {
+ c, err := getKrb(krbName, realm, keytabPath, krbConfPath)
+ if err != nil {
+ return nil, err
+ }
+ option.KerberosClient = c
+ option.KerberosServicePrincipleName = krbName
+ }
+
+ client, err := hdfs.NewClient(option)
+ if err != nil {
+ return nil, err
+ }
+
+ return client, nil
+
+}
+
+func getKrb(username, realm, ktPath, krbConfPath string) (*krb.Client, error) {
+ krb5conf, err := krbCfg.Load(krbConfPath)
+ if err != nil {
+ return nil, err
+ }
+ kt, err := krbKeytab.Load(ktPath)
+ if err != nil {
+ return nil, err
+ }
+ client := krb.NewWithKeytab(username, realm, kt, krb5conf)
+ return client, nil
+}
diff --git a/vermeer/apps/graphio/hugegraph.go b/vermeer/apps/graphio/hugegraph.go
new file mode 100644
index 00000000..f204324e
--- /dev/null
+++ b/vermeer/apps/graphio/hugegraph.go
@@ -0,0 +1,740 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with this
+work for additional information regarding copyright ownership. The ASF
+licenses this file to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+
+package graphio
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "math"
+ "net/http"
+ "strconv"
+ "strings"
+ "sync/atomic"
+ "time"
+ "vermeer/apps/common"
+ "vermeer/apps/options"
+ pd "vermeer/apps/protos/hugegraph-pd-grpc"
+ "vermeer/apps/protos/hugegraph-pd-grpc/metapb"
+ hstore "vermeer/apps/protos/hugegraph-store-grpc"
+ "vermeer/apps/serialize"
+ "vermeer/apps/structure"
+
+ "google.golang.org/grpc/metadata"
+
+ "github.com/sirupsen/logrus"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials/insecure"
+)
+
+func init() {
+ LoadMakers[LoadTypeHugegraph] = &HugegraphMaker{}
+}
+
+type HugegraphMaker struct{}
+
+func (a *HugegraphMaker) CreateGraphLoader() GraphLoader {
+ return &HugegraphLoader{}
+}
+
+func (a *HugegraphMaker) CreateGraphWriter() GraphWriter {
+ return &HugegraphWriter{}
+}
+
+func (a *HugegraphMaker) MakeTasks(params map[string]string, taskID int32) ([]LoadPartition, error) {
+ loadParts := make([]LoadPartition, 0)
+
+ //连接pd,获取图的分区列表
+ pdIPAddress := options.GetSliceString(params, "load.hg_pd_peers")
+ graphName := options.GetString(params, "load.hugegraph_name")
+ pdIPAddr, err := common.FindValidPD(pdIPAddress)
+ if err != nil {
+ return nil, err
+ }
+
+ //正式建立连接,进行查询
+ ctx, cancel1 := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel1()
+ pdConn, err := grpc.Dial(pdIPAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
+ if err != nil {
+ logrus.Errorf("connect hugegraph-pb err:%v", err)
+ return nil, err
+ }
+ pdAuthority := common.PDAuthority{}
+ ctx = pdAuthority.SetAuthority(ctx)
+ var md metadata.MD
+ pdClient := pd.NewPDClient(pdConn)
+ partitionsResp, err := pdClient.QueryPartitions(ctx,
+ &pd.QueryPartitionsRequest{Query: &metapb.PartitionQuery{GraphName: &graphName}}, grpc.Header(&md))
+ if err != nil {
+ logrus.Infof("QueryPartitions err:%v", err)
+ return nil, err
+ }
+ pdAuthority.SetToken(md)
+
+ // 获取用户设置的分区列表,如果为空,则获取所有分区
+ userSetPartitions := options.GetSliceInt(params, "load.hg_partitions")
+ userSetPartitionMap := make(map[uint32]struct{})
+ for _, partitionID := range userSetPartitions {
+ userSetPartitionMap[uint32(partitionID)] = struct{}{}
+ }
+ logrus.Debugf("user partition:%v", userSetPartitions)
+
+ partitions := partitionsResp.GetPartitions()
+
+ leaderStore := make(map[uint64]string)
+ partID := int32(1)
+ for _, partition := range partitions {
+ // 用户设置了分区列表,则只加载用户设置的分区
+ // 如果设置为空,则加载全部分区
+ if len(userSetPartitionMap) != 0 {
+ if _, ok := userSetPartitionMap[partition.Id]; !ok {
+ continue
+ }
+ }
+ var leaderStoreID uint64
+ ctx = pdAuthority.SetAuthority(ctx)
+ var md metadata.MD
+ shardGroup, err := pdClient.GetShardGroup(ctx, &pd.GetShardGroupRequest{GroupId: partition.Id}, grpc.Header(&md))
+ if err != nil {
+ logrus.Errorf("GetShardGroup err:%v", err)
+ return nil, err
+ }
+ pdAuthority.SetToken(md)
+ shards := shardGroup.GetShardGroup().GetShards()
+ for _, shard := range shards {
+ //找到partition的leader store_id
+ if shard.Role == metapb.ShardRole_Leader {
+ leaderStoreID = shard.StoreId
+ break
+ }
+ }
+ //重复leader不再执行获取地址
+ if _, ok := leaderStore[leaderStoreID]; !ok {
+ //获得新的leader的地址并写入map
+ ctx = pdAuthority.SetAuthority(ctx)
+ var md metadata.MD
+ storeResp, err := pdClient.GetStore(ctx, &pd.GetStoreRequest{StoreId: leaderStoreID}, grpc.Header(&md))
+ if err != nil {
+ logrus.Errorf("GetStore %v err:%v", leaderStoreID, err)
+ return nil, err
+ }
+ pdAuthority.SetToken(md)
+ if storeResp.Store.State != metapb.StoreState_Up {
+ logrus.Errorf("store:%v state not up:%v", storeResp.GetStore().Address, storeResp.Store.State)
+ logrus.Errorf("partition id:%v not available", partition.Id)
+ return nil, fmt.Errorf("store:%v state not up:%v", storeResp.GetStore().Address, storeResp.Store.State)
+ }
+ leaderStore[leaderStoreID] = storeResp.GetStore().Address
+ }
+ //将一个partition细分为n个load任务(暂不支持)
+ partitionCount := partition.EndKey - partition.StartKey
+ var n uint64 = 1
+ //parallel := uint64(options.GetInt(params, "load.parallel"))
+ //if partitionCount > parallel {
+ // n = parallel
+ //}
+ partCount := partitionCount / n
+ for i := uint64(0); i < n; i++ {
+ vStart := partition.StartKey + i*partCount
+ vEnd := vStart + partCount
+ if vEnd > partition.EndKey || i == n-1 {
+ vEnd = partition.EndKey
+ }
+
+ vertexPart := LoadPartition{}
+ vertexPart.Init(partID, taskID, LoadPartTypeVertex)
+ vertexPart.Params = make(map[string]string)
+ vertexPart.Params["graph_name"] = graphName
+ vertexPart.Params["part_type"] = LoadPartTypeVertex
+ vertexPart.Params["partition_id"] = strconv.FormatUint(uint64(partition.Id), 10)
+ vertexPart.Params["store_id"] = strconv.FormatUint(leaderStoreID, 10)
+ vertexPart.Params["store_address"] = leaderStore[leaderStoreID]
+ vertexPart.Params["start_key"] = strconv.FormatUint(vStart, 10)
+ vertexPart.Params["end_key"] = strconv.FormatUint(vEnd, 10)
+ loadParts = append(loadParts, vertexPart)
+ partID += 1
+
+ edgePart := LoadPartition{}
+ edgePart.Init(partID, taskID, LoadPartTypeEdge)
+ edgePart.Params = make(map[string]string)
+ edgePart.Params["graph_name"] = graphName
+ edgePart.Params["part_type"] = LoadPartTypeEdge
+ edgePart.Params["partition_id"] = vertexPart.Params["partition_id"]
+ edgePart.Params["store_id"] = vertexPart.Params["store_id"]
+ edgePart.Params["store_address"] = leaderStore[leaderStoreID]
+ edgePart.Params["start_key"] = vertexPart.Params["start_key"]
+ edgePart.Params["end_key"] = vertexPart.Params["end_key"]
+ loadParts = append(loadParts, edgePart)
+ partID += 1
+ }
+ }
+ for i := range loadParts {
+ loadParts[i].Params["load.hugegraph_conditions"] = params["load.hugegraph_conditions"]
+ loadParts[i].Params["load.vertex_property"] = params["load.vertex_property"]
+ loadParts[i].Params["load.edge_property"] = params["load.edge_property"]
+ loadParts[i].Params["load.hugegraph_vertex_condition"] = params["load.hugegraph_vertex_condition"]
+ loadParts[i].Params["load.hugegraph_edge_condition"] = params["load.hugegraph_edge_condition"]
+ loadParts[i].Params["load.hugestore_batch_timeout"] = params["load.hugestore_batch_timeout"]
+ loadParts[i].Params["load.hugestore_batchsize"] = params["load.hugestore_batchsize"]
+ }
+ err = pdConn.Close()
+ if err != nil {
+ logrus.Errorf("hugegraph-pd close err:%v", err)
+ return nil, err
+ }
+ return loadParts, nil
+}
+
+type HugegraphLoader struct {
+ handler *HStoreHandler
+ schema structure.PropertySchema
+ count int
+ partitionID uint32
+ startKey uint32
+ endKey uint32
+ graphName string
+ storeAddr string
+ useProperty bool
+ useLabel bool
+}
+
+func (hgl *HugegraphLoader) Init(params map[string]string, schema structure.PropertySchema) error {
+ if options.GetInt(params, "load.use_property") == 1 {
+ hgl.useProperty = true
+ }
+ hgl.useLabel = false
+ hgl.schema = schema
+ hgl.graphName = options.GetString(params, "graph_name")
+ partitionId, err := strconv.ParseUint(options.GetString(params, "partition_id"), 10, 32)
+ if err != nil {
+ logrus.Errorf("get uint partition_id err:%v", err)
+ return err
+ }
+ hgl.partitionID = uint32(partitionId)
+
+ storeAddress := options.GetString(params, "store_address")
+ hgl.storeAddr = storeAddress
+ startKey, err := strconv.ParseUint(options.GetString(params, "start_key"), 10, 64)
+ if err != nil {
+ logrus.Errorf("get uint start_key err:%v", err)
+ return err
+ }
+ hgl.startKey = uint32(startKey)
+
+ endKey, err := strconv.ParseUint(options.GetString(params, "end_key"), 10, 64)
+ if err != nil {
+ logrus.Errorf("get uint end_key err:%v", err)
+ return err
+ }
+ hgl.endKey = uint32(endKey)
+ var condition string
+ scanType := hstore.ScanPartitionRequest_SCAN_UNKNOWN
+ loadPartType := options.GetString(params, "part_type")
+ if loadPartType == LoadPartTypeVertex {
+ scanType = hstore.ScanPartitionRequest_SCAN_VERTEX
+ condition = options.GetString(params, "load.hugegraph_vertex_condition")
+ } else if loadPartType == LoadPartTypeEdge {
+ scanType = hstore.ScanPartitionRequest_SCAN_EDGE
+ condition = options.GetString(params, "load.hugegraph_edge_condition")
+ }
+
+ propertyLabels := make([]int64, 0)
+ if hgl.useProperty {
+ var propLabels []string
+ if loadPartType == LoadPartTypeVertex {
+ propLabels = strings.Split(options.GetString(params, "load.vertex_property"), ",")
+ } else if loadPartType == LoadPartTypeEdge {
+ propLabels = strings.Split(options.GetString(params, "load.edge_property"), ",")
+ }
+
+ for _, label := range propLabels {
+ if label == "" {
+ continue
+ }
+ if strings.TrimSpace(label) == "label" {
+ hgl.useLabel = true
+ continue
+ }
+ iLabel, err := strconv.ParseInt(label, 10, 64)
+ if err != nil {
+ logrus.Errorf("property schema label type not int :%v", label)
+ continue
+ }
+ propertyLabels = append(propertyLabels, iLabel)
+ }
+ } else {
+ propertyLabels = []int64{-1}
+ }
+ if loadPartType == LoadPartTypeEdge {
+ isVaild := false
+ for _, prop := range propertyLabels {
+ if prop >= 0 {
+ isVaild = true
+ break
+ }
+ }
+ if !isVaild {
+ hgl.useProperty = false
+ logrus.Debugf("edge load without property")
+ }
+ }
+ bacthSize := options.GetInt(params, "load.hugestore_batchsize")
+ scanRequest := &hstore.ScanPartitionRequest_ScanRequest{
+ ScanRequest: &hstore.ScanPartitionRequest_Request{
+ ScanType: scanType,
+ GraphName: hgl.graphName,
+ PartitionId: hgl.partitionID,
+ //StartCode: hgl.startKey,
+ //EndCode: hgl.endKey,
+ //Condition
+ Condition: condition,
+ Boundary: 0x10,
+ //需要返回的property id,不填就返回全部。
+ Properties: propertyLabels,
+ BatchSize: int32(bacthSize),
+ },
+ }
+
+ storeTimeout := options.GetInt(params, "load.hugestore_batch_timeout")
+ if storeTimeout < 60 {
+ storeTimeout = 60
+ }
+
+ hgl.handler = &HStoreHandler{}
+ err = hgl.handler.Init(scanRequest, storeAddress, int32(storeTimeout))
+ if err != nil {
+ logrus.Errorf("scan handler init error:%v", err)
+ return err
+ }
+
+ return nil
+}
+
+func (hgl *HugegraphLoader) ReadVertex(vertex *structure.Vertex, property *structure.PropertyValue) error {
+ hgVertex, err := hgl.handler.GetVertex()
+ if err != nil {
+ return err
+ }
+ vertex.ID, err = common.VariantToString(hgVertex.Id)
+ if err != nil {
+ return err
+ }
+
+ //read label
+ vLabel := serialize.SString(hgl.schema.HgPSchema.Labels[int(hgVertex.GetLabel())])
+ (*property)[0] = &vLabel
+ //read property
+ if hgl.useProperty {
+ property.LoadFromHugegraph(hgVertex.GetProperties(), hgl.schema)
+ }
+ hgl.count += 1
+ return nil
+}
+
+func (hgl *HugegraphLoader) ReadEdge(edge *structure.Edge, property *structure.PropertyValue) error {
+ hgEdge, err := hgl.handler.GetEdge()
+ if err != nil {
+ return err
+ }
+ edge.Source, err = common.VariantToString(hgEdge.SourceId)
+ if err != nil {
+ return err
+ }
+ edge.Target, err = common.VariantToString(hgEdge.TargetId)
+ if err != nil {
+ return err
+ }
+ if hgl.useLabel {
+ // read label
+ eLabel := serialize.SString(hgl.schema.HgPSchema.Labels[int(hgEdge.GetLabel())])
+ (*property)[0] = &eLabel
+ }
+ //read property
+ if hgl.useProperty {
+ property.LoadFromHugegraph(hgEdge.GetProperties(), hgl.schema)
+ }
+ hgl.count += 1
+ return nil
+}
+
+func (hgl *HugegraphLoader) Name() string {
+ return "hugegraph: " + hgl.graphName +
+ ", partition_id: " + strconv.FormatUint(uint64(hgl.partitionID), 10) +
+ ", store address: " + hgl.storeAddr +
+ ", start_key: " + strconv.FormatUint(uint64(hgl.startKey), 10) +
+ ", end_key: " + strconv.FormatUint(uint64(hgl.endKey), 10)
+}
+
+func (hgl *HugegraphLoader) ReadCount() int {
+ return hgl.count
+}
+
+func (hgl *HugegraphLoader) Close() {
+ hgl.handler.Close()
+}
+
+type HStoreHandler struct {
+ client hstore.GraphStore_ScanPartitionClient
+ scanType hstore.ScanPartitionRequest_ScanType
+ cancel context.CancelFunc
+ storeConn *grpc.ClientConn
+ vertices []*hstore.Vertex
+ edges []*hstore.Edge
+ storeAddr string
+ offset int
+ noResponseTime int32
+}
+
+func (hs *HStoreHandler) Init(scanRequest *hstore.ScanPartitionRequest_ScanRequest, storeAddress string, storeTimeout int32) error {
+ hs.offset = 0
+ hs.scanType = scanRequest.ScanRequest.ScanType
+ dialOptions := grpc.WithDefaultCallOptions(
+ grpc.MaxCallSendMsgSize(4*1024*1024*1024),
+ grpc.MaxCallRecvMsgSize(4*1024*1024*1024),
+ )
+ var err error
+ hs.storeConn, err = grpc.Dial(storeAddress, grpc.WithTransportCredentials(insecure.NewCredentials()), dialOptions)
+ if err != nil {
+ logrus.Errorf("connect hugegraph-store %v error:%v", storeAddress, err)
+ return err
+ }
+ storeClient := hstore.NewGraphStoreClient(hs.storeConn)
+ ctx, cancel1 := context.WithCancel(context.Background())
+ hs.cancel = cancel1
+ hs.client, err = storeClient.ScanPartition(ctx)
+ if err != nil {
+ logrus.Errorf("get scan client error:%v", err)
+ return err
+ }
+ err = hs.client.Send(&hstore.ScanPartitionRequest{Request: scanRequest})
+ if err != nil {
+ logrus.Errorf("hugegraph send scan request error:%v", err)
+ return err
+ }
+ hs.storeAddr = storeAddress
+
+ hs.noResponseTime = 0
+ go func() {
+ //监听时间,如果store超时无响应,cancel context
+ for {
+ time.Sleep(1 * time.Second)
+ atomic.AddInt32(&hs.noResponseTime, 1)
+ if atomic.LoadInt32(&hs.noResponseTime) >= storeTimeout {
+ logrus.Errorf("store:%v partition_id:%v has no response for more than %ds. closing transport",
+ storeAddress, scanRequest.ScanRequest.PartitionId, storeTimeout)
+ hs.cancel()
+ return
+ } else if hs.noResponseTime < 0 {
+ logrus.Infof("store:%v partition_id:%v is end. stopped listening", storeAddress, scanRequest.ScanRequest.PartitionId)
+ return
+ }
+ }
+ }()
+
+ return nil
+}
+
+func (hs *HStoreHandler) Scan() error {
+ resp, err := hs.client.Recv()
+ if err != nil {
+ if err != io.EOF {
+ logrus.Infof("hugegraph store:%v recv error:%v", hs.storeAddr, err)
+ }
+ _ = hs.client.CloseSend()
+ atomic.StoreInt32(&hs.noResponseTime, math.MinInt32)
+ return err
+ }
+
+ atomic.StoreInt32(&hs.noResponseTime, 0)
+ if hs.scanType == hstore.ScanPartitionRequest_SCAN_VERTEX {
+ hs.vertices = resp.Vertex
+ logrus.Debugf("get %d vertices", len(resp.Vertex))
+ } else if hs.scanType == hstore.ScanPartitionRequest_SCAN_EDGE {
+ hs.edges = resp.Edge
+ logrus.Debugf("get %d edges", len(resp.Edge))
+ }
+
+ err = hs.client.Send(&hstore.ScanPartitionRequest{
+ Request: &hstore.ScanPartitionRequest_ReplyRequest{
+ ReplyRequest: &hstore.ScanPartitionRequest_Reply{SeqNo: 1}}})
+ if err != nil && err != io.EOF {
+ logrus.Infof("hugegraph send error:%v", err)
+ }
+ return nil
+}
+
+func (hs *HStoreHandler) GetVertex() (*hstore.Vertex, error) {
+ if hs.offset == len(hs.vertices) {
+ err := hs.Scan()
+ if err != nil {
+ return nil, err
+ }
+ hs.offset = 0
+ }
+ vertex := hs.vertices[hs.offset]
+ hs.offset += 1
+ return vertex, nil
+}
+
+func (hs *HStoreHandler) GetEdge() (*hstore.Edge, error) {
+ if hs.offset == len(hs.edges) {
+ err := hs.Scan()
+ if err != nil {
+ return nil, err
+ }
+ hs.offset = 0
+ }
+ edge := hs.edges[hs.offset]
+ hs.offset += 1
+ return edge, nil
+}
+
+func (hs *HStoreHandler) Close() {
+ hs.cancel()
+ err := hs.client.CloseSend()
+ if err != nil {
+ logrus.Infof("hugegraph client close error:%v", err)
+ }
+ err = hs.storeConn.Close()
+ if err != nil {
+ logrus.Infof("hugegraph conn close error:%v", err)
+ }
+}
+
+type HugegraphWriter struct {
+ count int
+ delimiter string
+ writerURL string
+ serverAddr string
+ hgSpace string
+ hGraph string
+ username string
+ password string
+ writeBackName string
+ vertexIDStrategy map[string]common.HgVertexIDType
+ writerBody []vertexComputeValue
+ client *http.Client
+}
+
+func (hgw *HugegraphWriter) Init(info WriterInitInfo) error {
+ hgw.delimiter = options.GetString(info.Params, "output.delimiter")
+ hgName := options.GetString(info.Params, "output.hugegraph_name")
+ hgw.username = options.GetString(info.Params, "output.hugegraph_username")
+ hgw.password = options.GetString(info.Params, "output.hugegraph_password")
+ hgw.serverAddr = options.GetString(info.Params, "output.hugegraph_server")
+ hgw.writeBackName = options.GetString(info.Params, "output.hugegraph_property")
+ olapWriteType := options.GetString(info.Params, "output.hugegraph_write_type")
+ if hgw.writeBackName == "" {
+ hgw.writeBackName = options.GetString(info.Params, "compute.algorithm")
+ }
+ var err error
+ hgw.hgSpace, hgw.hGraph, err = common.SplitHgName(hgName)
+ if err != nil {
+ return err
+ }
+ hgw.client = http.DefaultClient
+
+ switch olapWriteType {
+ case "OLAP_COMMON", "OLAP_SECONDARY", "OLAP_RANGE":
+
+ default:
+ return fmt.Errorf("output.hugegraph_write_type options only support OLAP_COMMON,OLAP_SECONDARY,OLAP_RANGE")
+ }
+ //创建一个propertyKey
+ propertyKey := addPropertyKey{Name: hgw.writeBackName, WriteType: olapWriteType,
+ DataType: info.OutputType, Cardinality: "SINGLE"}
+ propKeyBytes, err := json.Marshal(propertyKey)
+ if err != nil {
+ return err
+ }
+ propURL := fmt.Sprintf("%s/graphspaces/%s/graphs/%s/schema/propertykeys", hgw.serverAddr, hgw.hgSpace, hgw.hGraph)
+ propReq, err := http.NewRequest(http.MethodPost, propURL, bytes.NewReader(propKeyBytes))
+ if err != nil {
+ return err
+ }
+ propReq.SetBasicAuth(hgw.username, hgw.password)
+ propReq.Header.Set("Content-Type", "application/json")
+ resp, err := hgw.client.Do(propReq)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+ logrus.Infof("set propertykeys status:%v", resp.Status)
+ if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
+ readAll, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+ return fmt.Errorf("hugegraph propertykeys request failed:%v", string(readAll))
+ }
+ hgw.vertexIDStrategy = make(map[string]common.HgVertexIDType)
+ //为hugegraph VertexLabel新增property
+ for _, labelName := range info.HgVertexSchema.HgPSchema.Labels {
+ vertexLabelURL := fmt.Sprintf("%s/graphspaces/%s/graphs/%s/schema/vertexlabels/%s?action=append",
+ hgw.serverAddr, hgw.hgSpace, hgw.hGraph, labelName)
+ vertexLabelBody := addVertexLabel{Name: labelName, Properties: []string{hgw.writeBackName},
+ NullableKeys: []string{hgw.writeBackName}}
+ vertexLabelBytes, err := json.Marshal(vertexLabelBody)
+ if err != nil {
+ return err
+ }
+ vertexLabelReq, err := http.NewRequest(http.MethodPut, vertexLabelURL, bytes.NewReader(vertexLabelBytes))
+ if err != nil {
+ return err
... 66626 lines suppressed ...