Skip to main content

· 10 min read
Likyh

近年来,开源正在变得越来越火,在很多开发者眼中,「开源」也是非常极客的体现。同时参与开源项目也能给职业发展带来巨大的好处。可一些小伙伴却因为不知道参与的方法和途径没能参与,这里就向大家介绍一下作为开发者,可以怎么拥抱开源软件,以及怎么成为大家认可的开源贡献者。

当然,本文会更多的从大的背景知识上进行叙述,关于代码提交的详细步骤,可以看看这一篇文章:如何参与开源项目 - 细说 GitHub 上的 PR 全过程

什么是开源软件

开源是源代码可以任意获取的计算机软件,任何人都能查看、修改和分发他们认为合适的代码,但这并不意味着可以使用源代码而没有任何著作权或发行权的约束,我们接触到的开源软件一般都有对应的开源许可证(Open Source License)对软件的使用、复制、修改和再发布等进行限制。许可证即授权条款,开源许可证就是说明这些限制的文件,常见的开源许可证主要有 Apache、MIT、BSD、GPL、LGPL、MPL、SSPL 等。

下面,我们通过一张表来简单了解一下常见宽松开源许可证之间的区别:

常见开源许可证之间的区别

(图片来自: https://www.ruanyifeng.com/blog/2011/05/how_to_choose_free_software_licenses.html)

其中,Apache 许可证(Apache License)是目前全球最大的开源软件基金会之一ASF (Apache Software Foundation) 发布的License。这是一个最初为 Apache http 服务器而撰写。此许可证最新版本于 2004 年 1 月发布,并要求所有ASF的项目均采用此项License。

寻找运行良好的开源项目

目前,开源项目主要是两类,一类由团队自行维护,一类由特定的基金会运行。现在大部分项目均托管在 GitHub 上,因此在GitHub上直接搜索点赞较高较活跃的项目,往往就是一个不错的选择。比如想参与数据分析相关的项目,可以在GitHub搜索 data analyzes,注意观察项目最近的提交时间和issue数量,更新快说明项目成员活跃,有issue说明是一个正在快速发展的项目,更适合参与。

image

第二种寻找方式,由开源基金会维护的运行良好的项目,还可以在对应的开源基金会官网找到。

比如Linux基金会的项目地址:https://www.linuxfoundation.org/projects/

Linux基金会的项目地址

比如CNCF的项目页:https://landscape.cncf.io/

CNCF的项目页

比如ASF的项目页:https://projects.apache.org/projects.html

ASF的项目页

最后,还有一些第三方的评估面板,从一些独特的视角了解现存的开源项目。 比如ossinsight:https://ossinsight.io/collections/open-source-database ,可以了解本月点赞最多的项目有些什么, ossinsight 再比如从 DevLake 的 OSS 项目面板 更加深入的了解项目,这都是了解开源项目运行状况的有效途径。 devlake oss

Apache开源软件基金会

因为笔者更了解 ASF ,所以这里就对它做一些更详细的介绍。

Apache 开源基金会目前维护着380余个开源项目,但一年的开销仅一百多万美元左右。这是一个非常低的数(而其他基金会比如Linux基金会,每年开销在上亿美元),平均每个项目仅2000余美元,这就决定了Apache的开源项目更依赖社区和开源贡献者,在Apache社区中,「Community over Code」即社区先于编码体现得淋漓尽致。Apache 基金会每年的支出其中80%用在基础设施,其余会用在营销、宣传和品牌管理服务,研讨会和发展社区,法律咨询等方面,而其他诸如日常维护、编码等工作均由各个项目的成员维护。

Apache开源项目中,一般有如下几个基本的角色:

  • Contributor 普通贡献者,这种就是很容易获得,只需要提交一个PR并被官方合并到主分支即可获得,例如提交一个文档,修改几行代码就行。

  • Committer 核心开发,对贡献特别大的 Contributor,官方社区会考虑将其吸收,提升到commiter,成为核心开发,此时就有项目的写入权限,并可以申请@apache.com结尾的邮箱

  • PMC 开源项目决策成员。

参与项目讨论

一旦选择好一个开源项目后,我们又该如何找到组织呢?

首先是阅读官方文档,全面了解该项目的架构设计文档和解决的问题,之后可以尝试参与项目日常的讨论。尽管在微信群中提问很方便,但 Apache 项目的大部分讨论需要公开地在邮件列表中进行,方便所有人查看及查询,因此我们也需要了解如何参与 Apache 的邮件讨论。

Apache下面的每一个项目都有自己的邮件列表,同时分不同的邮件组,以Apache DevLake为例,有如下订阅列表:

邮箱用途
user-subscribe@devlake.apache.org订阅该邮件可以参与讨论普通用户遇到的问题
dev-subscribe@devlake.apache.org订阅该邮件可以参与讨论开发者遇到的问题
commits-subscribe@devlake.apache.org所有的代码的提交变动信息都会发到该邮件

具体操作是首先给dev-subscribe@devlake.apache.org发一封邮件,等收到确认邮件后再次确认即可。

添加后就可以收到所有开发讨论的信息了,另外也可以关注官网的 maillist(https://lists.apache.org/list.html?dev@devlake.apache.org) 查看全部历史邮件。

另外,大部分项目会有一些线上的聚会,往往可以在Readme页面找到,参与线上聚会可以更直接的获取所需的信息,也能有机会和项目PMC直接交流。

向开源项目反馈问题

如果在项目的使用中,遇到了 bug,或者希望撸起袖子修改某个功能点,但这个功能点需要进一步讨论。可以在前面的邮件中发起讨论,当然也可以在 GitHub 的 issue 中做一个较正式的记录。

一般的项目都会针对不同的目的,提供一些 issue 创建的模板。

create issue

常见的类型有:

  • Bug 提出一个功能实现的错误
  • Document Issue 提出一项文档改进的建议
  • Feature Request 请求增加或表示你将增加一个产品特性
  • Refactor 发起一项不影响功能的重构
  • Security Vulnerability 报告一个安全问题,在问题修复以前,该问题不会公布。

提出一个清晰明了的 issue 往往会让社区的其他成员更愿意响应你的号召,相信我,这会是一个非常享受的过程~

成为项目的贡献者

在参与讨论的基础上,只要能在 GitHub 社区中帮助验证一些发布的新功能或者提一些建议或者缺陷,或者修改源码,就能成为该项目的贡献者(Contributor)。

刚参与项目时,可以考虑编写文档,或完善一些模块的单元测试,或者进行一些简单编码工作。比如可以在 GitHub Issues 列表中寻找带有good first issue标记且暂未被认领的事情,这往往是社区维护者为了引导贡献者专门创建的issue,很适合作为第一个提交。完成第一个提交后,可以再去看看其他open的issue并解决。

目前开源项目一般采用 Git 来管理源代码,如果你从未使用过代码管理工具的话,可以现在网上寻找教程了解,比如:https://www.liaoxuefeng.com/wiki/896043488029600 。一般的提交流程是:先 fork 对应的项目,在 fork 项目提交代码后,向开源项目发起代码合并请求等待合并。

需要注意的是,任何代码提交后,都不会立刻合并,需要寻找社区维护者 Review 后才会进入主干。

编写好代码的注意事项

img

(图片来自网络)

代码不是写完就好,还需要其他人阅读的。因此,写出赏心悦目的代码很值得点赞,当然,水平的提升总是有一个过程,因此任何开源项目都是鼓励尝试与提升的过程。这里就只说几点更容易得到社区成员帮助的注意事项:

  1. 写完代码提交 PR 后,注意在 PR 描述中补充完善的编码思路和背景知识,这会让其他成员更容易了解修改目标;
  2. 注意控制 PR 的大小,一个小的 PR 更容易让其他人了解全部修改项,如果有一个大的功能,可以按照模块拆成几个能分别运行的 PR;
  3. 注意补充适当的单元测试,因为 Reviewer 并不负责确定代码能跑,只负责看代码设计思路是否正确。因此增加合适的测试,能让 Reviewer 确定这段代码是可以运行的。有时 Reviewer 也会针对容易出错的地方提出补充测试的建议。

img

(图片来自网络,超大的提交会让 Reviewer 欲仙欲死的 😹)

Code Review 常见术语

在提交代码与Code Review的过程中,有时会遇到下面这些缩写,了解后参与开源社区更轻松。

缩写全称使用场景
-I'll take it.表示会尝试做这个任务
PR/MR/CRPull/Merge/Change Request如果要提交代码给开源项目,就会发起一个合并请求,在不同平台有不同的名字,但都是同一个东西
WIPWork In Progress表示PR尚未完成,暂时还不需要review
PTALPlease Take A Look请求项目维护人员进行 code review
TBDTo Be Done提示有一个事情需要完成
TL;DRToo Long; Didn't Read太长了,懒得看。也有时在文档中用作概览信息的标题
LGTM/SGTMLooks/Sounds Good To Me表示review完并觉得可以合并了,即Approve的意思
CC toCarbon Copy to抄送给

顺便再说几个 GitHub 中实用的小技巧:

  1. 如果你的 PR 解决了某个 issue,可以在描述中加上 close #1234,1234需要改成对应的issue号,在 PR 合并时该issue也会同时关闭(更多信息image-20220710215354120
  2. GitHub 的 Markdown 编辑器可以将默认字体设置为等宽字体,便于代码的书写,具体设置为: Settings - Appearance - Use a fixed-width (monospace) font when editing Markdown. (更多信息Screenshot showing the GitHub comment field with fixed width fonts enabled
  3. 当发现一个 issue 和其他 issue 重复时,可以设置在评论区留下 Duplicate of #1234 来标记与某个issue重复(更多信息Duplicate issue syntax

结语

好啦,大体的情况应该介绍的差不多了,其实参与开源项目并没有想象中的难,成为一个开源项目的Commiter,给职场和技术实力带来的助力将不可估量。另外,如果你还是较为初级的开发者,迫切的想要知道具体的参与步骤,可以看看开头提到的这篇文章,如何参与开源项目 - 细说 GitHub 上的 PR 全过程

我们的 DevLake (https://github.com/apache/incubator-devlake) 和 DevStream (https://github.com/devstream-io/devstream) 也是优秀的开源项目,欢迎你的参与哦~

· 3 min read
Geyu Chen

Apache DevLake is a dev data platform that can collect and integrate data from different dev tools including Jira, Github, Gitlab and Jenkins.

This blog will not aim at a comprehensive summary of the compatibility of database but a record of issues for future reference.

1.Different Data Types

PostgreSQL does not have a uint type

type JenkinsBuild struct {
common.NoPKModel
JobName string `gorm:"primaryKey;type:varchar(255)"`
Duration float64 // build time
DisplayName string // "#7"
EstimatedDuration float64
Number int64 `gorm:"primaryKey;type:INT(10) UNSIGNED NOT NULL"`
Result string
Timestamp int64 // start time
StartTime time.Time // convered by timestamp
CommitSha string
}

In JenkinsBuild.Number, thegormstruct tag used UNSIGNED, which will lead to the failure to create table and should be removed.

MySQL does not have a bool data type

For a field defined as bool type in model, gorm will map it to MySQL's TINYINT data type, which can be queried directly with 0 or 1 in SQL, but PostgreSQL has a bool type, so gorm will map it to the BOOL type. If 0 or 1 is still used in SQL to query, there will be a report of error.

Here is an example(only relevant fields are shown in the example). The lookup statement works in MySQL, but will lead to an error in PostgreSQL.

type GitlabMergeRequestNote struct {
MergeRequestId int `gorm:"index"`
System bool
}

db.Where("merge_request_id = ? AND `system` = 0", gitlabMr.GitlabId).

After changing the sentence as it follows, an error will still be reported. The reason will be shown in the part about backticks.

db.Where("merge_request_id = ? AND `system` = ?", gitlabMr.GitlabId, false)

2.Different Behaviors

Bulk insertion

When ON CONFLIT UPDATE ALL was used to achieve bulk insertion, and if there are multiple records with the same primary key, it will report errors in PostgreSQL but not in MySQL.

Inconsistent definition of model with schema

For example, in the model definition, GithubPullRequest.AuthorId is of the int type, but this field in the database is of VARCHAR type. When inserting data, MySQL will accept it, but ProstgresSQL will report an error.

type GithubPullRequest struct {
GithubId int `gorm:"primaryKey"`
RepoId int `gorm:"index"`
Number int `gorm:"index"`
State string `gorm:"type:varchar(255)"`
Title string `gorm:"type:varchar(255)"`
GithubCreatedAt time.Time
GithubUpdatedAt time.Time `gorm:"index"`
ClosedAt *time.Time
// In order to get the following fields, we need to collect PRs individually from GitHub
Additions int
Deletions int
Comments int
Commits int
ReviewComments int
Merged bool
MergedAt *time.Time
Body string
Type string `gorm:"type:varchar(255)"`
Component string `gorm:"type:varchar(255)"`
MergeCommitSha string `gorm:"type:varchar(40)"`
HeadRef string `gorm:"type:varchar(255)"`
BaseRef string `gorm:"type:varchar(255)"`
BaseCommitSha string `gorm:"type:varchar(255)"`
HeadCommitSha string `gorm:"type:varchar(255)"`
Url string `gorm:"type:varchar(255)"`
AuthorName string `gorm:"type:varchar(100)"`
AuthorId int
common.NoPKModel
}

3.MySQL-Specific Functions

We used the GROUP_CONCATfunction in a complex query. Although there are similar functions in PostgreSQL, the function names are different and the usage is slightly different.

cursor2, err := db.Table("pull_requests pr1").
Joins("left join pull_requests pr2 on pr1.parent_pr_id = pr2.id").Group("pr1.parent_pr_id, pr2.created_date").Where("pr1.parent_pr_id != ''").
Joins("left join repos on pr2.base_repo_id = repos.id").
Order("pr2.created_date ASC").
Select(`pr2.key as parent_pr_key, pr1.parent_pr_id as parent_pr_id, GROUP_CONCAT(pr1.base_ref order by pr1.base_ref ASC) as cherrypick_base_branches,
GROUP_CONCAT(pr1.key order by pr1.base_ref ASC) as cherrypick_pr_keys, repos.name as repo_name,
concat(repos.url, '/pull/', pr2.key) as parent_pr_url`).Rows()

Solution: We finally decided to use two steps to achieve the GROUP_CONCAT function. First we used the simplest SQL query to get multiple pieces of the sorted data, and then used the code to group them.

After modification:

    cursor2, err := db.Raw(
`
SELECT pr2.pull_request_key AS parent_pr_key,
pr1.parent_pr_id AS parent_pr_id,
pr1.base_ref AS cherrypick_base_branch,
pr1.pull_request_key AS cherrypick_pr_key,
repos.NAME AS repo_name,
Concat(repos.url, '/pull/', pr2.pull_request_key) AS parent_pr_url,
pr2.created_date
FROM pull_requests pr1
LEFT JOIN pull_requests pr2
ON pr1.parent_pr_id = pr2.id
LEFT JOIN repos
ON pr2.base_repo_id = repos.id
WHERE pr1.parent_pr_id != ''
ORDER BY pr1.parent_pr_id,
pr2.created_date,
pr1.base_ref ASC
`).Rows()

4.Different Grammar

Backticks

We used backticks in some SQL statements to protect field names from conflicting with MySQL reserved words, which can lead to errors in PostgreSQL. To solve this problem we revisited our code, modified all field names that conflict with reserved words, and removed the backticks in the SQL statement. In the example just mentioned:

db.Where("merge_request_id = ? AND `system` = ?", gitlabMr.GitlabId, false)

Solution: We changed system to is_system to avoid the usage of backticks.

db.Where("merge_request_id = ? AND is_system = ?", gitlabMr.GitlabId, false)

Non-standard delete statement

There were delete statements as followed in our code, which are legal in MySQL but will report an error in PostgreSQL.

err := db.Exec(`
DELETE ic
FROM jira_issue_commits ic
LEFT JOIN jira_board_issues bi ON (bi.source_id = ic.source_id AND bi.issue_id = ic.issue_id)
WHERE ic.source_id = ? AND bi.board_id = ?
`, sourceId, boardId).Error

· 19 min read
Nddtfjiang

本文作者:Nddtfjiang 个人主页:https://nddtf.com/github

什么是 计算提交版本差异(CalculateCommitsDiff)?

我们常常需要计算两个提交版本之间的差异。具体的说,就是需要知道两个不同的分支/标签之间相差了哪些提交版本

对于一般用户来说,通过计算提交版本差异,用户能迅速的判断两个不同的分支/标签之间在功能、BUG 修复等等方面的区别。以帮助用户选择不同的分支/标签来使用。

而如果只是使用 diff 命令来查看这两个不同的分支/标签的话,大量庞杂冗余的代码修改信息就会毫无组织的混杂在其中,要从中提取出具体的功能变更之类的信息,等同于大海捞针。

对于一款致力于提升研发效能的产品来说,通过计算提交版本差异,就能查看一组组不同的分支/标签的变迁状况,这一数据的获取,有助于我们做进一步的效能分析。

例如,当一个项目的管理者,想要看看为什么最近的几个版本发版越来越慢了的时候。就可以对最近的几组分支/标签来计算计算提交版本差异。此时有些分支/标签组之间有着大量的提交版本,而有些分支/标签组之间有着较少的提交版本。项目管理者可以更进一步的计算这些提交版本各自的代码当量,把这些数据以图表的形式展示出来,最终得到一组很直观的分支/标签的图像。此时他或许就能发现,原来是因为最近的几次发版涉及到的变更越来越复杂了。通过这样的直观的信息,开发者和管理者们都能做出相应的调整,以便提升研发效能。

已有的解决方案

当我们在 GitLab 上打开一个代码仓库的时候,我们可以通过在 url 末尾追加 compare 的方式来进入到仓库的比对页面。

在该页面,我们可以通过设置源分支/标签目标分支/标签GitLab 向我们展示 目标分支落后于源分支哪些版本,以及落后了多少个版本。

设置完毕后,GitLab 会展示如下:

在这里,我们能看到我们选择的目标分支/标签源分支/标签少了如图所示的提交版本(Commits)

然而遗憾的是,像 GitLab 这类解决方案,都没有做批量化,自动化的处理。也更没有对后续的计算出来的结果进行相应的数据汇总处理。用户面对海量的分支提交的时候,既不可能手动的一个一个去比较,也不可能手动的去把数据结果自己复制粘贴后再分析。

因此 DevLake 就必须要解决这个问题。

所谓的计算提交版本差异具体是在计算什么?

GitLab 的计算过程为例来说的话,所谓的计算提交版本差异也就是当一个提交版本源分支/标签存在,但是在目标分支/标签不存在的时候,这个提交版本就会被 GitLab 给逮出来。

那么,或许有人会问,假如一个提交版本源分支/标签不存在,相反的,在目标分支/标签存在,那是不是也会被抓起来呢?

答案是,不会

也就是说,当我们计算提交版本的差异的时候,我们只关心目标分支/标签缺少了什么,而并不关心目标分支/标签多出来了什么东西。

这就好像以前有一位算法竞赛的学生,在 NOI 比赛结束后被相关学校面试的时候,一个劲的自我介绍自己担任过什么广播站青协学生会,什么会长副会长之类的经历。结果很快就惹得面试官老师们忍无可忍的告诫道:

我们只想知道你信息学方面的自我介绍,其余的我都不感兴趣!!!

在计算提交版本差异时,GitLab 是这样。 GitHub 也是这样。事实上,在使用 git 命令 git log branch1...branch2 的时候,git 也是这样的。

它们都只关心目标分支/标签相对于源分支/标签缺少的部分。

计算提交版本差异实际上就是:

  • 计算待计算的目标分支/标签相对于源分支/标签缺少了哪些提交版本

提交版本进行数学建模

想要做计算,那么首先,我们需要把一个抽象的现实问题,转换成一个数学问题。

这里我们就需要进行数学建模了。

我们需要把像目标分支/标签源分支/标签提交版本 这样一系列的概念变成数学模型中的对象。

如此我们才能为其设计算法。

想当然的,我们就想到了使用图的方式来进行数学建模。

我们将每一个提交版本都看作是图上的一个节点,把提交版本合并之前的一组提交版本与当前提交版本之间的父子关系,看作成是一条有向边

由于目标分支源分支事实上也各自与一个特定的提交版本相绑定,因此也能将它们看作是图上的特别节点。

  • 目标分支/标签所对应的节点,命名为旧节点
  • 源分支/标签所对应的节点,命名为新节点

当然,这里我们还有一个需要特别关注的节点,就是初始的提交版本所代表的节点

  • 将初始提交版本所对应的节点,命名为根节点

上述的描述或许显得有点儿抽象。

我们现在来实际举一个例子。来看看如何对一个仓库进行上述数学建模。

假设现在有基于如下描述而产生的一个仓库:

  1. 创建空仓库
  2. main 分支上创建提交版本 1 作为初始提交
  3. main 分支上创建提交版本 2
  4. main 分支上创建新分支 nd
  5. nd 分支上创建提交版本 3
  6. main 分支上创建提交版本 4
  7. main 分支上创建新分支 dtf
  8. main 分支上创建提交版本 5
  9. dtf 分支上创建提交版本 6
  10. main 分支上创建新分支 nddtf
  11. nddtf 分支上创建提交版本 7
  12. nd 分支合并到 nddtf分支
  13. dtf 分支合并到 nddtf分支
  14. main 分支上创建提交版本 8
  15. nddtf 分支上创建提交版本 9

我们对上述的仓库进行构图之后,最终会得到如下图所示的一个有向图:

  • 此时彩色节点 1根节点
  • main 分支为 1 2 4 5 8
  • nd 分支为 1 2 3 随后合并入 nddtf 分支
  • dtf 分支为 1 2 4 6 随后合并入 nddtf 分支
  • nddtf 分支为 1 2 3 4 5 6 7 9

可以看到,每一个提交版本在图中都相对应的有一个节点

此时我们把提交版本 1 所代表的节点,作为根节点

当然这里可能会有同学提问了:

  • 假如我这个仓库有一万个根节点怎么破?

相信一些经常做图的建模的同学应该都知道破法。

  • 创建一个名叫为一万个根节点的虚拟节点,把它设为这些个虚假的根节点的父节点,来当作真正的根节点即可。

在这个有向图中,我们并没有实际的去指定具体的目标分支/标签或者源分支/标签

在实际使用中,我们可以把任意的两个提交版本作为一对目标分支/标签源分支/标签 当然,有的同学在这里可能又会产生一个问题:

  • 目标分支/标签源分支/标签 虽然都能映射到其最后的提交版本上,但是实际上来说提交版本分支/标签本质上就是两种不同的概念。

分支/标签的实质,是包含一系列的提交版本的集合。而特定的提交版本仅仅是这个集合中的最后一个元素罢了。

当我们把一个仓库通过上述数学建模抽象成一个有向图之后,这个集合的信息,会因此而丢失掉吗?

对于一个合法的仓库来说,答案显然是,不会

实际上,这也就是为什么我们一定要在该有向图中强调根节点的原因。

我们这里这里,先给出结论:

分支/标签所对应的节点,到根节点的全部路径中途径的所有节点的集合,即为该分支/标签所包含的提交版本集合。

简单证明 上述结论

  • 根节点为节点 A
  • 设要求的分支/标签所代表的节点为节点 B

  • 当 节点 C 是属于要求的分支/标签
  • 因为 节点 C 是属于要求的分支/标签
  • 所以 必然存在一组提交或者合并 使得 节点 C 可以一直提交到节点 B
  • 又因为 每一个新增的提交 或者 合并操作,均会切实的建立一条从新增的提交/合并到当前提交的边
  • 所以,反过来说,每一个提交或者合并后的节点,均可以抵达节点 C
  • 所以 节点 B 存在至少一条路径 可以 抵达节点 C
  • 同理可证,节点 C 存在至少一条路径抵达根节点 也就是节点 A
  • 综上,存在一条从节点 B 到节点 A 的路径,经过节点 C

  • 当 节点 C 不属于要求的分支/标签
  • 假设 存在一条从节点 B 到节点 A 的路径,经过节点 C
  • 因为 每一条边都是由新增的提交或者合并操作建立的
  • 所以 必然存在一系列的新增提交或者合并操作,使得节点 C 成为节点 B
  • 又因为 每一个提交在抽象逻辑上都是独一无二的
  • 因此,如果缺失了节点 C 则必然导致在构建节点 B 所代表的分支/标签的过程中,至少存在一个提交或者合并操作无法执行。
  • 这将导致分支非法
  • 因此 假设不成立
  • 因此 其逆否命题 对任意一条从节点 B 到节点 A 的路径,都不会经过节点 C 成立

  • 根据
  • 当 节点 C 是属于要求的分支/标签,存在一条从节点 B 到节点 A 的路径,经过节点 C (必要性)
  • 当 节点 C 不属于要求的分支/标签,对任意一条从节点 B 到节点 A 的路径,都不会经过节点 C (充分性)
  • 可得 分支/标签所对应的节点,到根节点的全部路径中途径的所有节点的集合,即为该分支/标签所包含的提交版本集合。

算法选择

我们现在已经完成了数学建模,并且已经为数学建模做了基本的证明。现在,我们终于可以开始在这个数学模型的基础上来设计并实现我们的算法了。

如果没有做上述基本论证的同学,这里可能会犯一个小错误:那就是它们会误以为,只要计算两个节点之间的最短路径即可。若真是如此的话,SPFA迪杰斯特拉(Dijkstra),甚至头铁一点儿,来个弗洛伊德(Floyd)都是很容易想到的。当然由于该有向图的所有边长都是 1,所以更简单的方法是直接使用广/宽度优先搜索算法(BFS)来计算最短路。

上述的一系列耳熟能详的算法,或多或少都有成熟的库可以直接使用。但是遗憾的是,如果真的是去算最短路的话,那最终结果恐怕会不尽如人意。

DevLake 的早期不成熟的版本中,曾经使用过最短路的算法来计算。尽管对于比较简单线性的仓库来说,可以歪打正着的算出结果。但是当仓库的分支和合并变得复杂的时候,最短路所计算的结果往往都会遗漏大量的提交版本

因为在刚才我们已经论证过了,这个分支/标签所包含的提交版本集合,是必须要全部路径才行的。只有全部路径,才能满足充分且必要条件。

也就是说,中间只要漏了一条路径,那就会漏掉一系列的提交版本

要计算这个有向图上的旧节点所代表的分支/标签新节点所代表的分支/标签缺少了哪些提交版本

实质上就是在计算旧节点根节点的全部路径所经节点,对比新节点根节点的全部路径所经节点,缺少了哪些节点。

如果我们数学建模的时候,把这个有向图建立成一棵树的话。

那么熟悉算法的同学,就可以很自然的使用最近公共祖先(LCA)算法,求出其并集,然后再来计算其对应的缺失的部分。

但是作为一个有向图来说,树结构的算法是没法直接使用的。所幸的是,我们的这个图,在由合法仓库生成的情况下,必然是一个有向无环图。

一个有向无环图,也是有自己的最近公共祖先(LCA)算法的。

只是,这里有两个问题:

  • 我们真的对 最近公共祖先 这个特定的节点感兴趣吗?
  • 在有多个不同路径的公共祖先的情况下,只求一个最近公共祖先有什么意义呢?

首先,我们需要明确我们的需求。

我们只是为了计算 。

  • 旧节点根节点的全部路径所经节点,对比新节点根节点的全部路径所经节点,缺少了哪些节点。

除此之外的,我们不感兴趣。

换句话说,我们想知道其公共祖先,但是,不关心它是不是最近的。

它是近的也好,它是远的也罢,只要是公共祖先,都得抓起来。去求最近公共祖先,在树结构下,可以近似等价于求其全部祖先。因此可以使用该算法。

但是在有向无环图下,最近公共祖先就是个废物。求出来了又能如何?

根本上,还是应该去求全部的公共祖先。

所以我们别无选择,只能用最直接的算法。

  • 计算出旧节点根节点的全部路径所经节点
  • 计算出新节点根节点的全部路径所经节点
  • 检查新节点的全部路径所经节点缺少了哪些节点

如何计算任意节点到根节点的全部路径所经节点?

在 OI 上熟练于骗分导论的同学们,应该很自然的就意识到了

深度优先搜索(DFS)

当然,这里补充一下,由于根节点的性质,事实上,无论是从哪个方向出发,无论走那条边,只要是能走的边,最终都会抵达根节点

因此,在上述条件成立的基础上,没有记录路径状态的广/宽度优先搜索(BFS)也是可以使用的。因为在必然能抵达根节点的前提下,可以忽略路径状态,不做路径的可行性判断。

当然,这一前提,也有利于我们深度优先搜索(DFS)进行优化。

在我们执行深度优先搜索(DFS)的时候,我们可以将所有访问到的节点,均添加到集合中,而无需等待确认该节点能确实抵达根节点后再进行添加。

实际上这里在一个问题上我们又会出现了两种分歧。 问题是,如何将一个节点添加到集合中。方案有如下两种。

染色法:添加到集合中的节点进行染色,未添加到集合中的节点不进行染色。 集合法:使用平衡树算法建立一个集合,将节点添加到该集合中。

这两种算法各有优劣。

  • 染色法的优势在于,染色法添加一个元素的时间复杂度是 O(1) 的,快准狠。相比较而言,集合法添加一个元素的时间复杂度是 O(log(n))。
  • 集合法的优势在于,集合法遍历所有元素的时间复杂度是 O(n) 的,而染色法下,要遍历所有元素时间复杂度会是 O(m),同时集合法可以通过设计一个优秀的 hash 算法代替平衡树,来将时间复杂度优化到接近 O(1).(这里 n 表示集合大小,m 表示整个图的大小)

我们这里选择使用集合法。实际上这两种算法都差不多。

算法实现

  • 根据提交建图
  • 我们对旧节点使用深度优先搜索(DFS)计算出其到根节点的全部路径所经节点,添加到集合 A
  • 接着,我们对新节点使用深度优先搜索(DFS)计算出其到根节点的全部路径所经节点,添加到集合 B
  • 注意,这里有一个优化,这个优化是建立在我们的需求上
  • 重复一遍我们的需求
  • 我们只关心目标分支/标签缺少了什么,而并不关心目标分支/标签多出来了什么东西。
  • 因此当对新节点使用深度优先搜索(DFS)搜索到已经在集合 A 中的节点时,可以认为该节点已搜索过,无需再次搜索。
  • 此时的集合 B,可以恰好的避开集合 A 中已有的所有节点,因此,恰好就是我们所需的结果。

核心的计算代码如下所示:

oldGroup := make(map[string]*CommitNode)
var dfs func(*CommitNode)
// put all commit sha which can be depth-first-searched by old commit
dfs = func(now *CommitNode) {
if _, ok = oldGroup[now.Sha]; ok {
return
}
oldGroup[now.Sha] = now
for _, node := range now.Parent {
dfs(node)
}
}
dfs(oldCommitNode)

var newGroup = make(map[string]*CommitNode)
// put all commit sha which can be depth-first-searched by new commit, will stop when find any in either group
dfs = func(now *CommitNode) {
if _, ok = oldGroup[now.Sha]; ok {
return
}
if _, ok = newGroup[now.Sha]; ok {
return
}
newGroup[now.Sha] = now
lostSha = append(lostSha, now.Sha)
for _, node := range now.Parent {
dfs(node)
}
}
dfs(newCommitNode)

这里的 lostSha 即为我们最终求得的缺失的部分

算法执行的演示动画

我们用一个简陋的动画来简单的演示一下,上述算法在逻辑上执行的情况。

  • 旧节点为节点 8
  • 新节点为节点 9

如上述动画所演示的一般 从节点 8 开始执行深度优先搜索(DFS)根节点中止 从节点 9 开始执行深度优先搜索(DFS)到已经在节点 8 的集合中的节点为止 此时,在节点 9 执行深度优先搜索(DFS)过程中被访问到的所有非节点 8 的节点

  • 节点 3
  • 节点 6
  • 节点 7
  • 节点 9

它们所对应的提交版本就是我们要求的差集

此时最短路为 9 -> 7 -> 5 -> 8 此时最近公共父节点为 5,到该节点的路径为 9 -> 7 -> 5 从上图中也可以直观的看到如果使用最短路算法,或者最近公共父节点算法的情况下,我们是无法得到正确答案的。

时空复杂度

提交版本的总大小为 m,每一组源分支/标签目标分支/标签的平均大小为 n,一共有 k 组数据

DFS 每访问一个节点,需要执行一次加入集合操作。我们按照我们实际实现中使用的 平衡树算法来计算 时间复杂度为 O(log(n))

此时我们可以计算得出

  • 建图的时间复杂度:O(m)
  • 计算一组源分支/标签目标分支/标签时间复杂度:O(n*log(n))
  • 计算所有源分支/标签目标分支/标签时间复杂度:O(k*n*log(n))
  • 读取、统计结果时间复杂度:O(k*n)
  • 总体时间复杂度:O(m + k*n*log(n))

  • 图的空间复杂度:O(m)
  • 每组源分支/标签目标分支/标签集合的空间复杂度:O(n) (非并发情况下,k组数据可共用)
  • 总体空间复杂度:O(m+n)

关键词

  • DevLake
  • CalculateCommitsDiff
  • 算法
  • 数学建模
  • 证明逻辑
  • 充分条件
  • 必要条件
  • 图论
  • 深度优先搜索(DFS)
  • 广/宽度优先搜索(BFS)
  • 时间复杂度
  • 空间复杂度
  • 时空复杂度

了解更多最新动态

官网:https://devlake.incubator.apache.org/

GitHub:https://github.com/apache/incubator-devlake/

Slack:通过 Slack 联系我们

· 4 min read
ZhangLiang

本文作者:ZhangLiang
个人主页:https://github.com/mindlesscloud

Apache DevLake 是一个研发数据平台,可以收集和整合各类研发工具的数据,比如 Jira、Github、Gitlab、Jenkins。

本文并不打算对数据库兼容这个问题做全面的总结,只是对我们实际遇到的问题做一个记录,希望能对有相似需求的人提供一个参考。

1、数据类型差异

PostgreSQL 不支持 uint 类型的数据类型

type JenkinsBuild struct {
common.NoPKModel
JobName string `gorm:"primaryKey;type:varchar(255)"`
Duration float64 // build time
DisplayName string // "#7"
EstimatedDuration float64
Number int64 `gorm:"primaryKey;type:INT(10) UNSIGNED NOT NULL"`
Result string
Timestamp int64 // start time
StartTime time.Time // convered by timestamp
CommitSha string
}

其中的JenkinsBuild.Number字段的gorm struct tag 使用了UNSIGNED会导致建表失败,需要去掉。

MySQL 没有 bool 型

对于 model 里定义为 bool 型的字段,gorm 会把它映射成 MySQL 的 TINYINT 类型,在 SQL 里可以直接用 0 或者 1 查询,但是 PostgreSQL 里是有 bool 类型的,所以 gorm 会把它映射成 BOOL 类型,如果 SQL 里还是用的 0 或者 1 去查询就会报错。

以下是一个具体的例子(为了清晰起见我们删掉了无关的字段),下面的查询语句在 MySQL 里是没有问题的,但是在 PostgreSQL 上就会报错。

type GitlabMergeRequestNote struct {
MergeRequestId int `gorm:"index"`
System bool
}

db.Where("merge_request_id = ? AND `system` = 0", gitlabMr.GitlabId).

语句改成这样后仍然会有错误,具体请见下面关于反引号的问题。

db.Where("merge_request_id = ? AND `system` = ?", gitlabMr.GitlabId, false)  

2、行为差异

批量插入

如果使用了ON CONFLIT UPDATE ALL从句批量插入的时候,本批次如果有多条主键相同的记录会导致 PostgreSQL 报错,MySQL 则不会。

字段类型 model 定义与 schema 不一致

例如在 model 定义中GithubPullRequest.AuthorId是 int 类型,但是数据库里这个字段是 VARCHAR 类型,插入数据的时候 MySQL 是允许的,PostgreSQL 则会报错。

type GithubPullRequest struct {
GithubId int `gorm:"primaryKey"`
RepoId int `gorm:"index"`
Number int `gorm:"index"`
State string `gorm:"type:varchar(255)"`
Title string `gorm:"type:varchar(255)"`
GithubCreatedAt time.Time
GithubUpdatedAt time.Time `gorm:"index"`
ClosedAt *time.Time
// In order to get the following fields, we need to collect PRs individually from GitHub
Additions int
Deletions int
Comments int
Commits int
ReviewComments int
Merged bool
MergedAt *time.Time
Body string
Type string `gorm:"type:varchar(255)"`
Component string `gorm:"type:varchar(255)"`
MergeCommitSha string `gorm:"type:varchar(40)"`
HeadRef string `gorm:"type:varchar(255)"`
BaseRef string `gorm:"type:varchar(255)"`
BaseCommitSha string `gorm:"type:varchar(255)"`
HeadCommitSha string `gorm:"type:varchar(255)"`
Url string `gorm:"type:varchar(255)"`
AuthorName string `gorm:"type:varchar(100)"`
AuthorId int
common.NoPKModel
}

3、MySQL 特有的函数

在一个复杂查询中我们曾经使用了 GROUP_CONCAT 函数,虽然 PostgreSQL 中有功能类似的函数但是函数名不同,使用方式也有细微差别。

cursor2, err := db.Table("pull_requests pr1").
Joins("left join pull_requests pr2 on pr1.parent_pr_id = pr2.id").Group("pr1.parent_pr_id, pr2.created_date").Where("pr1.parent_pr_id != ''").
Joins("left join repos on pr2.base_repo_id = repos.id").
Order("pr2.created_date ASC").
Select(`pr2.key as parent_pr_key, pr1.parent_pr_id as parent_pr_id, GROUP_CONCAT(pr1.base_ref order by pr1.base_ref ASC) as cherrypick_base_branches,
GROUP_CONCAT(pr1.key order by pr1.base_ref ASC) as cherrypick_pr_keys, repos.name as repo_name,
concat(repos.url, '/pull/', pr2.key) as parent_pr_url`).Rows()

解决方案: 我们最终决定把GROUP_CONCAT函数的功能拆分成两步,先用最简单的 SQL 查询得到排序好的多条数据,然后用代码做聚合。

修改后:

cursor2, err := db.Raw(
`
SELECT pr2.pull_request_key AS parent_pr_key,
pr1.parent_pr_id AS parent_pr_id,
pr1.base_ref AS cherrypick_base_branch,
pr1.pull_request_key AS cherrypick_pr_key,
repos.NAME AS repo_name,
Concat(repos.url, '/pull/', pr2.pull_request_key) AS parent_pr_url,
pr2.created_date
FROM pull_requests pr1
LEFT JOIN pull_requests pr2
ON pr1.parent_pr_id = pr2.id
LEFT JOIN repos
ON pr2.base_repo_id = repos.id
WHERE pr1.parent_pr_id != ''
ORDER BY pr1.parent_pr_id,
pr2.created_date,
pr1.base_ref ASC
`).Rows()

4、语法差异

反引号

某些 SQL 语句中我们使用了反引号,用来保护字段名,以免跟 MySQL 保留字有冲突,这种做法在 PostgreSQL 会导致语法错误。为了解决这个问题我们重新审视了我们的代码,把所有跟保留字冲突的字段名做了修改,同时去掉了 SQL 语句中的反引号。例如刚才提到的这个例子:

db.Where("merge_request_id = ? AND `system` = ?", gitlabMr.GitlabId, false)

解决方案:我们把system改个名字is_system,这样就可以把反引号去掉。

db.Where("merge_request_id = ? AND is_system = ?", gitlabMr.GitlabId, false)

不规范的删除语句

我们的代码中曾经出现过这种删除语句,这在 MySQL 中是合法的,但是在 PostgreSQL 中会报语法错误。

err := db.Exec(`
DELETE ic
FROM jira_issue_commits ic
LEFT JOIN jira_board_issues bi ON (bi.source_id = ic.source_id AND bi.issue_id = ic.issue_id)
WHERE ic.source_id = ? AND bi.board_id = ?
`, sourceId, boardId).Error

解决方案:我们把DELETE后面的表别名去掉就可以了。

了解更多最新动态

官网:https://devlake.incubator.apache.org/

GitHub:https://github.com/apache/incubator-devlake/

Slack:通过 Slack 联系我们

· 4 min read
Danna Wang

Apache DevLake is an integration tool with the DevOps data collection functionality, which presents a different stage of data to development teams via Grafana. which also can leverage teams to improve the development process with a data-driven model.

Apache DevLack Architecture Overview

  • The left side of the following screenshot is an integrative DevOps data plugin, the existing plugins include Github, GitLab, JIRA, Jenkins, Tapd, Feishu, and the most featured analysis engine in the Simayi platform.
  • The main framework in the middle of the following screenshot, completes data collection, expansion, and conversion to the domain layer by running subtasks in the plugins. The user can trigger the tasks by config-UI or all API.
  • RMDBS currently supports Mysql and PostgresSQL, more databases will be supported in the future.
  • Grafana can generate different types of needed data by using SQL.

Generated

Then let’s move on to how to start running DevLake.

· 9 min read
abeizn

Apache DevLake是什么?

研发数据散落在软件研发生命周期的不同阶段、不同工作流、不同DevOps工具中,且标准化程度低,导致效能数据难以留存、汇集并转化为有效洞见。为了解决这一痛点,Apache DevLake 应运而生。Apache DevLake是一款开源的研发数据平台,它通过提供自动化、一站式的数据收集、分析以及可视化能力,帮助研发团队更好地理解开发过程,挖掘关键瓶颈与提效机会。

Apache DevLake架构概述

img

Apache DevLake 架构图

  • Config UI: 人如其名,配置的可视化,其主要承载Apache DevLake的配置工作。通过Config UI,用户可以建立数据源连接,并实现数据的收集范围,部分数据的转换规则,以及收集频率等任务。
  • Api Sever:Apache DevLake的Api接口,是前端调用后端数据的通道。
  • Runner:Apache DevLake运行的底层支撑机制。
  • Plugins:具体执行的插件业务,主要承载Apache DevLake的后端数据收集、扩展和转换的工作。除dbt插件外的插件产出Apache DevLake的预置数据,预置数据主要包括三层;
    • raw layer:负责储存最原始的api response json。
    • tool layer:根据raw layer提取出此插件所需的数据。
    • domain layer:根据tool layer层抽象出共性的数据,这些数据会被使用在Grafana图表中,用于多种研发指标的展示。
  • RDBS: 关系型数据库。目前Apache DavLake支持MySQL和PostgreSQL,后期还会继续支持更多的数据库。
  • Grafana Dashboards: 其主要承载Apache DevLake的前端展示工作。根据Apache DevLake收集的数据,通过sql语句来生成团队需要的交付效率、质量、成本、能力等各种研发效能指标。

目录结构Tree

├── api
│   ├── blueprints
│   ├── docs
│   ├── domainlayer
│   ├── ping
│   ├── pipelines
│   ├── push
│   ├── shared
│   ├── task
│   └── version
├── config
├── config-ui
├── devops
│   └── lake-builder
├── e2e
├── errors
├── grafana
│   ├── _archive
│   ├── dashboards
│   ├── img
│   └── provisioning
│   ├── dashboards
│   └── datasources
├── img
├── logger
├── logs
├── migration
├── models
│   ├── common
│   ├── domainlayer
│   │   ├── code
│   │   ├── crossdomain
│   │   ├── devops
│   │   ├── didgen
│   │   ├── ticket
│   │   └── user
│   └── migrationscripts
│   └── archived
├── plugins
│   ├── ae
│   │   ├── api
│   │   ├── models
│   │   │   └── migrationscripts
│   │   │   └── archived
│   │   └── tasks
│   ├── core
│   ├── dbt
│   │   └── tasks
│   ├── feishu
│   │   ├── apimodels
│   │   ├── models
│   │   │   └── migrationscripts
│   │   │   └── archived
│   │   └── tasks
│   ├── gitextractor
│   │   ├── models
│   │   ├── parser
│   │   ├── store
│   │   └── tasks
│   ├── github
│   │   ├── api
│   │   ├── models
│   │   │   └── migrationscripts
│   │   │   └── archived
│   │   ├── tasks
│   │   └── utils
│   ├── gitlab
│   │   ├── api
│   │   ├── e2e
│   │   │   └── tables
│   │   ├── impl
│   │   ├── models
│   │   │   └── migrationscripts
│   │   │   └── archived
│   │   └── tasks
│   ├── helper
│   ├── jenkins
│   │   ├── api
│   │   ├── models
│   │   │   └── migrationscripts
│   │   │   └── archived
│   │   └── tasks
│   ├── jira
│   │   ├── api
│   │   ├── models
│   │   │   └── migrationscripts
│   │   │   └── archived
│   │   └── tasks
│   │   └── apiv2models
│   ├── refdiff
│   │   ├── tasks
│   │   └── utils
│   └── tapd
│   ├── api
│   ├── models
│   │   └── migrationscripts
│   │   └── archived
│   └── tasks
├── releases
│   ├── lake-v0.10.0
│   ├── lake-v0.10.0-beta1
│   ├── lake-v0.10.1
│   ├── lake-v0.7.0
│   ├── lake-v0.8.0
│   └── lake-v0.9.0
├── runner
├── scripts
├── services
├── test
│   ├── api
│   │   └── task
│   └── example
├── testhelper
├── utils
├── version
├── worker
├── Dockerfile
├── docker-compose.yml
├── docker-compose-temporal.yml
├── k8s-deploy.yaml
├── Makefile
└── .env.exemple


目录导览

  • 后端部分:
    • config:对.env配置文件的读、写以及修改的操作。
    • logger:log日志的level、format等设置。
    • errors:Error的定义。
    • utils:工具包,它包含一些基础通用的函数。
    • runner:提供基础执行服务,包括数据库,cmd,pipelines,tasks以及加载编译后的插件等基础服务。
    • models:定义框架级别的实体。
      • common:基础struct定义。
      • domainlayer:领域层是来自不同工具数据的通用抽象。
        • ticket:Issue Tracking,即问题跟踪领域。
        • code:包括Source Code源代码关联领域。以及Code Review代码审查领域。
        • devops:CI/CD,即持续集成、持续交付和持续部署领域。
        • crossdomain:跨域实体,这些实体用于关联不同领域之间的实体,这是建立全方面分析的基础。
        • user:对用户的抽象领域,user也属于crossdomain范畴。
      • migrationscripts:初始化并更新数据库。
    • plugins:
      • core:插件通用接口的定义以及管理。
      • helper:插件通用工具的集合,提供插件所需要的辅助类,如api收集,数据ETL,时间处理等。
        • 网络请求Api Client工具。
        • 收集数据Collector辅助类,我们基于api相同的处理模式,统一了并发,限速以及重试等功能,最终实现了一套通用的框架,极大地减少了开发和维护成本。
        • 提取数据Extractor辅助类,同时也内建了批量处理机制。
        • 转换数据Convertor辅助类。
        • 数据库处理工具。
        • 时间处理工具。
        • 函数工具。
      • ae:分析引擎,用于导入merico ae分析引擎的数据。
      • feishu:收集飞书数据,目前主要是获取一段时间内组织内会议使用的top用户列表的数据。
      • github:收集Github数据并计算相关指标。(其他的大部分插件的目录结构和实现功能和github大同小异,这里以github为例来介绍)。
        • github.go:github启动入口。
        • tasks:具体执行的4类任务。
          • *_collector.go:收集数据到raw layer层。
          • *_extractor.go:提取所需的数据到tool layer层。
          • *_convertor.go:转换所需的数据到domain layer层。
          • *_enricher.go:domain layer层更进一步的数据计算转换。
        • models:定义github对应实体entity。
        • api:api接口。
        • utils:github提取的一些基本通用函数。
      • gitextractor:git数据提取工具,该插件可以从远端或本地git仓库提取commit和reference信息,并保存到数据库或csv文件。用来代替github插件收集commit信息以减少api请求的数量,提高收集速度。
      • refdiff:在分析开发工作产生代码量时,经常需要知道两个版本之间的diff。本插件基于数据库中存储的commits父子关系信息,提供了计算ref(branch/tag)之间相差commits列表的能力。
      • gitlab:收集Gitlab数据并计算相关指标。
      • jenkins:收集jenkins的build和job相关指标。
      • jira:收集jira数据并计算相关指标。
      • tapd:收集tapd数据并计算相关指标。
      • dbt:(data build tool)是一款流行的开源数据转换工具,能够通过SQL实现数据转化,将命令转化为表或者视图,提升数据分析师的工作效率。Apache DevLake增加了dbt插件,用于数据定制的需要。
    • services:创建、管理Apache DevLake各种服务,包含notifications、blueprints、pipelines、tasks、plugins等。
    • api:使用Gin框架搭建的一个通用Apache DevLake API服务。
  • 前端部分:
    • congfig-ui:主要是Apache DevLake的插件配置信息的可视化。一些术语的解释
      • 常规模式
        • blueprints的配置。
        • data connections的配置。
        • transformation rules的配置。
      • 高级模式:主要是通过json的方式来请求api,可选择对应的插件,对应的subtasks,以及插件所需要的其他信息。
    • Grafana:其主要承载Apache DevLake的前端展示工作。根据收集的数据,通过sql语句来生成团队需要的各种数据。目前sql主要用domain layer层的表来实现通用数据展示需求。
  • migration:数据库迁移工具。
    • migration:数据库迁移工具migration的具体实现。
    • models/migrationscripts:domian layer层的数据库迁移脚本。
    • plugins/xxx/models/migrationscripts:插件的数据库迁移脚本。主要是rawtool开头的数据库的迁移。
  • 测试部分:
    • testhelper和plugins下的*_test.go文件:即单元测试,属于白盒测试范畴。针对目标对象自身的逻辑,执行路径的正确性进行测试,如果目标对象有依赖其它资源或对够用,采用注入或者 mock 等方式进行模拟,可以比较方便地制造一些难以复现的极端情况。
    • test:集成测试,灰盒测试范畴。在单元测试的基础上,将所有模块按照设计要求(如根据结构图)组装成为子系统或系统,进行集成测试。
    • e2e: 端到端测试,属于黑盒测试范畴。相对于单元测试更注重于目标自身,e2e更重视目标与系统其它部分互动的整体正确性,相对于单元测试着重逻辑测试,e2e侧重于输出结果的正确性。
  • 编译,发布部分:
    • devops/lake-builder: mericodev/lake-builder的docker构建。
    • Dockerfile:主要用于构建devlake镜像。
    • docker-compose.yml:是用于定义和运行多容器Docker应用程序的工具,用户可以使用YML文件来配置Apache DevLake所需要的服务组件。
    • docker-compose-temporal.yml:Temporal是一个微服务编排平台,以分布式的模式来部署Apache DevLake,目前处于试验阶段,仅供参考。
    • worker:Temporal分布式部署形式中的worker实现,目前处于试验阶段,仅供参考。
    • k8s-deploy.yaml:Kubernetes是一个可移植、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。目前Apache DevLake已支持在k8s集群上部署。
    • Makefile:是一个工程文件的编译规则,描述了整个工程的编译和链接等规则。
    • releases:Apache DevLake历史release版本的配置文件,包括docker-compose.yml和env.example。
    • scripts:shell脚本,包括编译plugins脚本。
  • 其他:
    • img:logo、社区微信二维码等图像信息。
    • version:实现版本显示的支持,在正式的镜像中会显示对应release的版本。
    • .env.exemple:配置文件实例,包括DB URL, LOG以及各插件的配置示例信息。

如何联系我们

· 2 min read
Klesh Wong

上周(2022-05-12),我们以先到先得的方式为大家列出了两个"good first issue"。 这很有趣,它们几乎立刻就被拿走了...... 但对于那些有兴趣但没有得到的人来说可能就不那么有趣了。

所以...

我们决定,不再有竞争,你可以从我们的github issue pages中挑选你喜欢的issue。如果没有了,甚至可以创建你自己的。 我们毕竟是社区!

怎么做呢?这很简单!

进入我们的问题页面,然后点击这里。我们所有的Good First Issue都列在这里! good first issue

  • 首先,寻找现有的issues,找到一个你喜欢的。 你可以通过评论"I'll take it!"来预订它。 接下来你可以写一份“攻略”,以展示你对问题的理解和你将采取什么样的步骤来解决这个issue,然后开始Coding。

  • 如果没有GFI了怎么办?创造你自己的issue! 现在,通过查看我们的代码库。 你肯定能发现很多问题,比如文档、单元测试,甚至是错字。 把你觉得不对的地方提出来,我们会验证它是否必要, 然后你就可以开始Coding了。

  • 最后,你可能会问,我为什么要费尽心思为你写代码? 不不不,你不是为我们写代码,你是为社区里的每个人写代码,你是为自己写代码。 为了提高你的技能,为了学习如何与他人合作。而对于那些做出重大贡献的人, 我们为您提供一个Apache Committer的席位,甚至是PPMC!

就这些了,有任何问题请随时提出。编码快乐!

· 2 min read
Maxim Wheatley

We are excited to share today that the Apache Software Foundation (ASF) voted to make DevLake an officially supported project of the Apache Incubator.

What is DevLake?

Launched in December of 2021, Apache DevLake is an open-source dev data platform that ingests, analyzes, and visualizes the fragmented data in developer tools.

Software development is complex, requiring many tools and processes, and as a result creates a storm of data scattered across tools in many formats. This makes it difficult to organize, query, and make sense of. We built Apache DevLake, to make it easy to make sense of this rich data and to translate it into actionable insights.

· 5 min read
Warren Chen

Apache DevLake 是一个DevOps数据收集和整合工具,通过 Grafana 为开发团队呈现出不同阶段的数据,让团队能够以数据为驱动改进开发流程。

Apache DevLake 架构概述

  • 左边是可集成的DevOps数据插件,目前已有的插件包括 Github,Gitlab,JIRA,Jenkins,Tapd,Feishu 以及思码逸主打的代码分析引擎
  • 中间是主体框架,通过主体框架运行插件中的子任务,完成数据的收集,扩展,并转换到领域层,用户可以通过 config-ui 或者 api 调用的形式来触发任务
  • RMDBS 目前支持 Mysql 和 PostgreSQL,后期还会继续支持更多的数据库
  • Grafana 可以通过sql语句生成团队需要的各种数据

Generated

接下来我们就详细聊一聊系统是怎么跑起来的。

· 6 min read
Warren Chen

1. 背景

我们的项目有大量的api请求由goroutine完成,所以我们需要引入一个pool来节省频繁创建goroutine所造成的的开销,同时也可以更简易的调度goroutine,在对github上多个协程池的对比后,我们最终选定了ants作为我们的调度管理pool。

  1. 最近在测试中偶然发现系统出现了“死锁”的情况,进而采取断网的方式发现“死锁”在极端情况下是稳定出现,经过满篇的log,break,最终把问题定位到了ants的submit方法。这个问题来自于在使用ants pool的过程中,为了实现重试,我们在方法中又递归调用了方法本身,也就是submit task内部又submit一个task,下面是简化后的代码: