建設個公司網(wǎng)站需要多少費用個人在百度上發(fā)廣告怎么發(fā)
引語:作為國內(nèi)外最大的代碼托管平臺,根據(jù)最新的 GitHub 數(shù)據(jù),它擁有超 372,000,000 個倉庫,其中有 28,000,000 是公開倉。分布式圖數(shù)據(jù)庫 NebulaGraph 便是其中之一,同其他開源項目一樣,NebulaGrpah 也有自己的 contributor 們,他們是何時,通過哪個 pr 與 NebulaGraph 產(chǎn)生聯(lián)系的呢?本文嘗試用可視化方式,來探索這些 contributor 的痕跡。
世界上有兩種需求,一種是能做的,另外一種是不能做的;當然按照合理不合理角度,大多數(shù)的需求都是合理但能做的,就像本文的需求一樣——用可視化的方式,來“窺探” nebula 開源社區(qū)中 contributor 同項目的關系,及他們留下的 pr 痕跡。
故事從兩個月前講起,有一天我司研發(fā) liuyu 同學裝了一款名叫 ClickHouse 的數(shù)據(jù)庫,他發(fā)現(xiàn) CK 有一個感人的 contributor 系統(tǒng)表,這不得讓我們的運營來“借鑒”下么?
現(xiàn)在,我們來看看感動我司研發(fā)的 ClickHouse 是怎么樣的存在。
讓人感動的 ClickHouse Contributor 系統(tǒng)表
簡單來說,只要你裝了 CK 數(shù)據(jù)庫,不需要連接任何數(shù)據(jù)庫,系統(tǒng)自帶一個數(shù)據(jù)表,你可以執(zhí)行以下 SQL
select count() from system.contributors
就能得到一個現(xiàn)有的 CK contributor 總量(下面數(shù)據(jù)存在一定滯后性):
也可以按照下列方式隨機獲得 20 位 contributor 名單:
select * from system.contributors limit 20;
這種用 SQL 方式查看 contributor 的方式還挺 cool 的,畢竟 contributor 是一群通過提交 pr 來完善、迭代產(chǎn)品的人,其中很大一部分的 contributor 是工程師,SQL 更是信手拈來。
現(xiàn)在問題來了,作為一個不會寫 SQL 的運營,如何滿足我司研發(fā)提出的讓他感動一下的 contributor 系統(tǒng)表?冷靜下,ClickHouse 的這個 SQL 看 contributor 的方式固然很酷,但是終歸到底是要查看貢獻者同開源項目的關系。說到“搞關系”,還不得是我們的圖數(shù)據(jù)庫。巧的是,NebulaGraph 就是一款圖數(shù)據(jù)庫,雖然在本文的數(shù)據(jù)集過于簡單用,也不是什么大規(guī)模數(shù)據(jù),用圖數(shù)據(jù)庫有點“殺雞用牛刀”,但不妨一試??纯?#xff0c;不會寫 SQL 的運營怎么用可視化的方式來查看 contributor 和項目關系。
看得見的 contributor 和 pr 關系
效果先行,在這個章節(jié),我們來看下 NebulaGraph 開源社區(qū)的 contributor 和 pr 情況,而這些數(shù)據(jù)是如何生成、展示的實操部分在后面。
開源社區(qū)全覽
這里收錄了所有 NebulaGraph 相關的公開倉的貢獻情況,大概是這樣的:
加上時序之后,能看到一個個 contributor(方形圖)出現(xiàn)在畫布上,同各個 repo(圓形圖)連接在一起。這里僅僅展示了所有 contributor 第一次提交 pr,更多的查詢在后面的「可視化圖探索」部分。
下面的章節(jié)為實操內(nèi)容,一起看看如何生成可視化的 contributor 和開源項目的關系圖吧。
手把手帶你可視化探索數(shù)據(jù)
下面著重介紹下本文的可視化工具——NebulaGraph Explorer,具體介紹看文檔:https://docs.nebula-graph.com.cn/3.4.1/nebula-explorer/about-explorer/ex-ug-what-is-explorer/。對我而言,Explorer 有兩大特點:易上手、所見即所得。我可以白嫖我司線上 Explorer 環(huán)境,不用搭建自己的數(shù)據(jù)庫就能直接用,當然你如果想和我一樣有個免費的線上環(huán)境,估計得用 NebulaGraph Cloud,它配有可視化圖探索工具 NebulaGrpah Explorer。
用來進行數(shù)據(jù)探索的工具有了,現(xiàn)在就是數(shù)據(jù)哪里來的問題了。
簡單建模
在采集數(shù)據(jù)之前,我們需要簡單建模(我從未見過如此簡單的圖模型)了解需要采集的數(shù)據(jù)。下圖為圖模型:
這個圖模型中有兩種點類型:repo
和 contributor
,它們之間由 pr
這個邊聯(lián)系在一起構(gòu)成了最基礎的點邊圖模型。在分布式圖數(shù)據(jù)庫 NebulaGraph 中點的類型用 tag 來表示,邊類型有 edgetype,一個點可以有若干種 tag,點的 ID 為 vid,像是你的身份證一樣為唯一標識。
- tag
- repo,擁有倉庫名
name
,主要編程語言language
以及倉庫路徑path
等三種屬性; - contributor,擁有貢獻者名
name
,貢獻者編號number
,誕生日anniversary
,是否為 NebulaGraph 開發(fā)商雇員is_vesoft
,第一個被合并 pr 所屬倉first_repo
。加入了判斷“是否為 NebulaGraph 開發(fā)商雇員”的屬性是為了避免超大節(jié)點,因為一個企業(yè)雇員的 pr 產(chǎn)量不同于其他的非雇員貢獻者。(這點會在后面的可視化展示中體現(xiàn))
- repo,擁有倉庫名
- edgetype
- pr,擁有 pr 編號
number
,提交時間created_time
,關閉時間closed_time
,合并時間merged_time
,是否被合并is_merged
,變更情況:ins_code_line
、des_code_line
、file_number
。上面的時間字段可以用來篩選出某個時間區(qū)間里的 pr 邊;
- pr,擁有 pr 編號
contributor 數(shù)據(jù)采集
下面這段代碼是拜托我司優(yōu)秀的 IT 工程師喬治編寫的,那些需要配置、填上你自己信息的地方,我用注釋進行了標注:
# Copyright @Shinji-IkariG
from github import Github
from datetime import datetime
import sh
from sh import curl
import csv
import requests
import timedef main():
# 你的 GitHub IDGH_USER = 'xxx'
# 你的個人 token,可以前往 GitHub 設置中的 Developer settings 生成自己的 tokenGH_PAT = 'xxx'github = Github(GH_PAT)
# 你需要爬取的開源組織的組織名org = github.get_organization('vesoft-inc')repos = org.get_repos(type='all', sort='full_name', direction='asc')
# 命名存放爬下來的 pr 數(shù)據(jù)的文件with open('all-prs.csv', 'w', newline='') as csvfile:
# 爬取哪些數(shù)據(jù)fieldnames = ['pr num','repo','author', 'create date','close date','merged date','version','labels1','state','branch','assignee','reviewed(commented)','reviewd(approved)','request reviewer','code line(+)','code line(-)','files number']writer = csv.DictWriter(csvfile, fieldnames=fieldnames)writer.writeheader()for repo in repos:print(repo)Apulls = repo.get_pulls(state='all', sort='created')prs = []for a in Apulls:prs.append(a)for i in prs:github = Github(GH_PAT)print('rate_limite' , github.rate_limiting[0])if github.rate_limiting[0] < 500:if github.rate_limiting_resettime - time.time() > 0:time.sleep(github.rate_limiting_resettime - time.time()+900)else:time.sleep(3700)else:print(i.number)prUrl = 'https://api.github.com/repos/'+ str(repo.full_name) + '/pulls/' + str(i.number)pr = requests.get(prUrl, auth=(GH_USER, GH_PAT))assigneesList = []if pr.json().get('assignees'):for assignee in pr.json().get('assignees'):assigneesList.append(assignee.get('login'))else: ""reviewerCList = []reviewerAList = []reviewers = requests.get(prUrl + '/reviews', auth=(GH_USER, GH_PAT))if reviewers.json():for reviewer in reviewers.json():if reviewer.get('state') == 'COMMENTED':if reviewer.get('user'): reviewerCList.append(reviewer.get('user').get('login'))else: reviewerCList.append('GHOST USER')elif reviewer.get('state') == 'APPROVED':if reviewer.get('user'): reviewerAList.append(reviewer.get('user').get('login'))else: reviewerAList.append('GHOST USER')else : print(reviewer.get('state'), 'TYPE REVIEWS')else: ""reqReviewersList = []reqReviewers = requests.get(prUrl + '/requested_reviewers', auth=(GH_USER, GH_PAT))if reqReviewers.json().get('users'):for reqReviewer in reqReviewers.json().get('users'):reqReviewersList.append(reqReviewer.get('login'))print(reqReviewersList)else: ""labelList = []if pr.json().get('labels'):for label in pr.json().get('labels'):labelList.append(label.get('name'))else: ""milestone = pr.json().get('milestone').get('title') if pr.json().get('milestone') else ""writer.writerow({'pr num': i.number,'repo': repo.full_name,'author': pr.json().get('user').get('login'), 'create date': pr.json().get('created_at'),'close date': pr.json().get('closed_at'),'merged date': pr.json().get('merged_at'),'version': milestone,'labels1': ",".join(labelList),'state': pr.json().get('state'),'branch': pr.json().get('base').get('ref'),'assignee': ",".join(assigneesList),'reviewed(commented)': ",".join(reviewerCList),'reviewd(approved)': ",".join(reviewerAList),'request reviewer': ",".join(reqReviewersList),'code line(+)': pr.json().get('additions'),'code line(-)': pr.json().get('deletions'),'files number': pr.json().get('changed_files')})if __name__ == "__main__":main()#pip3 install sh pygithub
等你運行完上面代碼,便能得到一個名叫 “all-prs.csv”。腳本爬取的是 vesoft-inc(NebulaGraph 開發(fā)商)組織下的所有倉,這里并沒有區(qū)分倉庫狀態(tài),這就意味著它也會將私有倉的數(shù)據(jù)爬取下來。因此,我們要對數(shù)據(jù)進行二次處理。這里略過我簡單處理數(shù)據(jù)的過程,處理完的 pr 數(shù)據(jù)中可以抽取相關的 contributor 數(shù)據(jù)。
上面提到過每個點都有 vid,因此將 contributor 的 vid 設定為他/她的 GitHub ID,repo 的 vid 則采用縮寫,而邊的數(shù)據(jù)中起點和終點就為上面的 contributor vid 和 repo vid。
現(xiàn)在我們有了,contributor.csv,pr.csv,repo.csv 三個文件,格式類似:
# contributor.csv
wenhaocs,haowen,148,2021-09-24 16:53:33,1,nebula
lopn,lopn,149,2021-09-26 06:02:11,0,nebula-docs-cn
liwenhui-soul,liwenhui-soul,150,2021-09-26 13:38:20,1,nebula
Reid00,Reid00,151,2021-10-08 06:20:24,0,nebula-http-gateway
...# pr.csv
nevermore3,nebula,4095,2022-03-29 11:23:15,2022-04-13 03:29:44,2022-04-13 03:29:44,1,2310,3979,31
cooper-lzy,docs_cn,1614,2022-03-30 03:21:35,2022-04-07 07:28:31,2022-04-07 07:28:31,1,107,2,4
wuxiaobai24,nebula,4098,2022-03-30 05:51:14,2022-04-11 10:54:04,2022-04-11 10:54:03,1,53,0,3
NicolaCage,website,876,2022-03-30 06:08:02,2022-03-30 06:09:21,2022-03-30 06:09:21,1,4,2,1
...#repo.csv
clients,nebula-clients,vesoft-inc/nebula-clients,Java
common,nebula-common,vesoft-inc/nebula-common,C++
community,nebula-community,vesoft-inc/nebula-community,Markdown
console,nebula-console,vesoft-inc/nebula-console,Go
...
數(shù)據(jù)導入
數(shù)據(jù)導入之前需要創(chuàng)建相關的 Schema 進行數(shù)據(jù)映射。
創(chuàng)建 Schema
現(xiàn)在我們需要把圖結(jié)構(gòu)模型變成 NebulaGraph 能識別的 Schema,有兩種方式來創(chuàng)建 Schema:一是用查詢語言 nGQL 來編寫 Schama,另外一種則是用可視化圖探索工具 NebulaGraph Explorer 提供的可視化界面填寫信息完成。和我一樣對查詢語言不熟悉的小伙伴,建議首選后者。
登陸到 NebulaGraph Explorer 之后,先創(chuàng)建一個圖空間(類似 MySQL 中的 Table):
效果同下面的 nGQL 語言:
# nebula-contributor-2023 是這個圖空間名字,其他默認;
CREATE SPACE 'nebula-contributor-2023'(partition_num = 10, vid_type = FIXED_STRING(32))
創(chuàng)建完圖空間之后,再創(chuàng)建兩個點類型和一個邊類型,二者創(chuàng)建方式類似。
下面,以創(chuàng)建相對復雜的 contributor 點類型為例:
同效于這條 nGQL 語句:
CREATE tag contributor (name string NULL, number int16 NULL, anniversary datetime NULL, is_vesoft bool NULL, first_merged string NULL) COMMENT = "貢獻者"
同樣的 repo 和 pr 邊可以用下面的 nGQL 或同上圖一樣用 Explorer。
# 創(chuàng)建 repo tag
CREATE tag repo (repo_name string NULL, language string NULL, path string NULL) COMMENT = "倉庫"# 創(chuàng)建 pr edge
CREATE edge pr (number int NULL, created_time datetime NULL, closed_time datetime NULL DEFAULT NULL, merged_time datetime NULL DEFAULT NULL, is_merged bool NULL, ins_code_line int NULL, des_code_line int NULL, file_changed_num int NULL)
導入數(shù)據(jù)
因為用了可視化工具 Explorer,所以上傳數(shù)據(jù)也可以用“看得見的方法”。在創(chuàng)建完 Schema 之后,點擊這個右上角的菜單欄“Import”,開始數(shù)據(jù)導入。
數(shù)據(jù)源選擇本地,找到上面準備的 3 個 csv 文件所在路徑,把文件上傳之后。開始【導入】過程,在這個步驟主要是完成本地數(shù)據(jù)文件同 Schema 的關聯(lián)。類似下圖:
在整個數(shù)據(jù)集中,我們有兩種點:vertices 1 關聯(lián) repo 的 csv 數(shù)據(jù),vertices 2 關聯(lián) contributor 數(shù)據(jù),指定各自的 VID 和相關屬性的所在列之后,就可以導入數(shù)據(jù)了。在邊數(shù)據(jù)關聯(lián)這塊,因為我們之前已經(jīng)在 csv 中加入了 repo 和 contributor 的各自 VID,所以這里同點的關聯(lián)一樣,簡單勾選哪列是起點(Column 0)、哪列是終點(對應上圖的 Column 1)。
需要進行特殊說明的是,因為一個 contributor 和一個 repo 會存在多次提交 pr 記錄,即:多條同 pr 邊類型的邊。而對同一類型邊的處理問題,圖數(shù)據(jù)庫 NebulaGraph 引入了 rank
字段來表示兩個點之間多條同一類型,但邊屬性不同的邊。如果你不設定 rank,插入多條同一類型邊,則會進行數(shù)據(jù)覆蓋操作,以最后成功插入的邊數(shù)據(jù)為準。
為了偷懶,這里 rank 我直接用了 pr 編號 number
列,仔細看,上面的 rank
和 number
都是讀取的同一列 Column 2 數(shù)據(jù)。
可視化圖探索
現(xiàn)在我們有數(shù)據(jù)了,可以進入到可視化圖探索模式了。
在“Visual Query”菜單下,拖拽兩個 tag:contributor 和 repos,選擇 pr 邊,【運行】,就能看到所有 contributor 提交的 pr 數(shù)據(jù)。它的效果等同于下面這句 nGQL 查詢語言:
match (v0:contributor) -[e:pr]-> (v1:repo) return e limit 15000
我們隨意加入一點像是下面這種小細節(jié):
我們把點的頭像全部換下,這里為了節(jié)省時間找研發(fā)小哥龍仔開了個綠色通道批量上傳了 contributor 和 repo 點的頭像?,F(xiàn)在,整圖的效果展示是這樣的:
因為 nebula 最大的貢獻來源于其雇員(員工),所以這里我們除去雇員,查看下非雇員的貢獻情況,效果同查詢語言:
match (v0:contributor) -[e:pr]-> (v1:repo) where (v0.contributor.is_vesoft == false) return e limit 15000
上圖是將 nGQL 查詢結(jié)果導入到畫布,對應的 NebulaGraph Explorer 操作為點擊【導入圖探索】,再進行同類型邊合并,放大 contributor 點的大小,選擇輻射模式,就呈現(xiàn)了最終效果:
看看倉庫編程語言為 C++、Python、Go、Java 各自的貢獻者情況:
可以看到,內(nèi)核倉 nebula 采用了 C++,不少相關的周邊工具也用了 C++。因此,整個開源項目中 C++ 的貢獻者(點)還是比較多的。反之,目前只有 Python 客戶端 nebula-python、同步工具 auto_sync 和安裝工具 nebula-ansible 使用 Python 語言開發(fā),因此相較于其他編程語言,contributor 數(shù)量并不多。
說到內(nèi)核倉,我們來看看內(nèi)核倉 nebula 的非雇員貢獻者情況:
通過合并同類型 pr 邊,根據(jù)邊的粗細我們可以看到核心倉的活躍貢獻者。留意上面那個 Java logo 的圖像,并非是 nebula 同 Java 聯(lián)誼了,而是 2020 年的 Committer ChenXU 用了 Java 的 logo 作為頭像(狗頭)。
再來看看 2021 年誕生的非雇員 contributor 他們的貢獻情況:
最后,來看看有哪些 pr 還沒被 merge,這里需要用到 pr 邊的 is_merged
屬性(記得創(chuàng)建個索引哦~):
祝上面所有未被 merged 的 pr 都能被合并(雖然這是不可能的)。
nGQL 合集
這里是上面所有查詢結(jié)果的對應 nGQL 查詢語句:
# 查看各個查詢語言的開源倉庫貢獻情況
match (v0:contributor) -[e:pr]-> (v1:repo) where (v1.repo.language == "C++") return ematch (v0:contributor) -[e:pr]-> (v1:repo) where (v1.repo.language == "Python") return ematch (v0:contributor) -[e:pr]-> (v1:repo) where (v1.repo.language == "Go") return ematch (v0:contributor) -[e:pr]-> (v1:repo) where (v1.repo.language == "Java") return e# 內(nèi)核倉 nebula 的非雇員貢獻者match (v0:contributor) -[e:pr]-> (v1:repo) where (v1.repo.repo_name == "nebula" and v0.contributor.is_vesoft == false) return e# 2021 年誕生的非雇員 contributor
match (v0:contributor) -[e:pr]-> (v1:repo) where (v0.contributor.anniversary >= datetime("2021-01-01T00:00:00") and v0.contributor.anniversary < datetime("2022-01-01T00:00:00") ) and v0.contributor.is_vesoft ==false return e# 目前未被合并的 pr
match (v0:contributor) -[e:pr]-> (v1:repo) where (e.is_merged == false) return e
數(shù)據(jù)集
本數(shù)據(jù)集為 NebulaGraph 公開倉數(shù)據(jù),統(tǒng)計截止時間為 2023.03.20。因為部分 datetime 屬性不能為空,為空字段人為填充了為 2038-01-19 03:14:07
(timestamp 類型上限)。如果你要使用該數(shù)據(jù)集,記得留意 datetime 屬性值的處理。
數(shù)據(jù)集下載地址:nebula-contributor-dataset
最后,以此文感謝所有 nebula 社區(qū)的 contributor 們 lol
謝謝你讀完本文 (///▽///)