提交 7bcb2ac8 编写于 作者: O openeuler-ci-bot 提交者: Gitee

!1 wisdom-advisor package init

Merge pull request !1 from MarsChan/master
Wenliang He <hewenliang4@huawei.com>
Kang Zhou <zhoukang7@huawei.com>
Kai Shen <shenkai8@huawei.com>
Kemeng Shi <shikemeng@huawei.com>
木兰宽松许可证, 第2版
木兰宽松许可证, 第2版
2020年1月 http://license.coscl.org.cn/MulanPSL2
您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束:
0. 定义
“软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。
“贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。
“贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。
“法人实体”是指提交贡献的机构及其“关联实体”。
“关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。
1. 授予版权许可
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。
2. 授予专利许可
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。
3. 无商标许可
“本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。
4. 分发限制
您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。
5. 免责声明与责任限制
“软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。
6. 语言
“本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。
条款结束
如何将木兰宽松许可证,第2版,应用到您的软件
如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步:
1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字;
2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中;
3, 请将如下声明文本放入每个源文件的头部注释中。
Copyright (c) [Year] [name of copyright holder]
[Software Name] is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
Mulan Permissive Software License,Version 2
Mulan Permissive Software License,Version 2 (Mulan PSL v2)
January 2020 http://license.coscl.org.cn/MulanPSL2
Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions:
0. Definition
Software means the program and related documents which are licensed under this License and comprise all Contribution(s).
Contribution means the copyrightable work licensed by a particular Contributor under this License.
Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License.
Legal Entity means the entity making a Contribution and all its Affiliates.
Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity.
1. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not.
2. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken.
3. No Trademark License
No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4.
4. Distribution Restriction
You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software.
5. Disclaimer of Warranty and Limitation of Liability
THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
6. Language
THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL.
END OF THE TERMS AND CONDITIONS
How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software
To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps:
i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner;
ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package;
iii Attach the statement to the appropriate annotated syntax at the beginning of each source file.
Copyright (c) [Year] [name of copyright holder]
[Software Name] is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
**OPEN SOURCE SOFTWARE NOTICE**
Please note we provide an open source software notice along with this product and/or this product firmware (in the following just “this product”). The open source software licenses are granted by the respective right holders. And the open source licenses prevail all other license information with regard to the respective open source software contained in the product, including but not limited to End User Software Licensing Agreement. This notice is provided on behalf of Huawei Technologies Co. Ltd. and any of its local subsidiaries which may have provided this product to you in your local country.
**Warranty Disclaimer**
THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
**Copyright Notice and License Texts**
**Software**: github.com/sirupsen/logrus v1.4.2
**Copyright notice:**
Copyright (c) 2014 Simon Eskildsen
**License:** MIT
Copyright \<YEAR\> \<COPYRIGHT HOLDER\>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**Software**: github.com/urfave/cli v1.20.0
**Copyright notice:**
Copyright (c) 2016 Jeremy Saenz & Contributors
**License:** MIT
Please see above.
**Software**: github.com/cpuguy83/go-md2man/v2 v2.0.0
**Copyright notice:**
Copyright (c) 2014 Brian Goff
**License:** MIT
Please see above.
**Software**: github.com/konsorten/go-windows-terminal-sequences v1.0.1
**Copyright notice:**
Copyright (c) 2017 marvin + konsorten GmbH (open-source@konsorten.de)
**License:** MIT
Please see above.
**Software**: github.com/russross/blackfriday/v2 v2.0.1
**Copyright notice:**
Copyright © 2011 Russ Ross
**License:** BSD
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided with
the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
**Software**: github.com/shurcooL/sanitized\_anchor\_name
**Copyright notice:**
Copyright (c) 2015 Dmitri Shuralyov
**License:** MIT
Please see above.
**Software**: golang.org/x/sys v0.0.0
**Copyright notice:**
Copyright (c) 2009 The Go Authors.
**License:** BSD-3-Clause
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.PHONY: all clean base check coverage install
PREFIX=/usr
SBINDIR=$(DESTDIR)$(PREFIX)/sbin
PKGPATH=pkg
CURDIR=$(shell pwd)
XARCH=$(shell arch)
X86_64=x86_64
ARRCH64=aarch64
VERSION=1.0
all: wisdomd wisdom
wisdomd:
export GOPATH=`cd ../../../;pwd`;\
export PATH=$$GOPATH/bin:$$PATH;\
go build -mod=vendor -ldflags '-w -s -extldflags=-static -extldflags=-zrelro -extldflags=-znow -buildmode=pie -tmpdir /tmp -X main.version=${VERSION}' -v -o $$GOPATH/pkg/wisdomd $$GOPATH/src/gitee.com/wisdom-advisor/cmd/wisdomd
wisdom:
export GOPATH=`cd ../../../;pwd`;\
export PATH=$$GOPATH/bin:$$PATH;\
go build -mod=vendor -ldflags '-w -s -extldflags=-static -extldflags=-zrelro -extldflags=-znow -buildmode=pie -tmpdir /tmp -X main.version=${VERSION}' -v -o $$GOPATH/pkg/wisdom $$GOPATH/src/gitee.com/wisdom-advisor/cmd/wisdom
format :
export GOPATH=`cd ../../..;pwd` && gofmt -e -s -w cmd
export GOPATH=`cd ../../..;pwd` && gofmt -e -s -w common
clean:
export GOPATH=`cd ../../..;pwd`;\
rm -rf $$GOPATH/$(PKGPATH)/*;\
rm -rf $$GOPATH/bin/*;\
rm -rf ./cov
check:
export GOPATH=`cd ../../..;pwd`;\
export PATH=$$GOPATH/bin:$$PATH;\
go test -mod=vendor ./... -count=1
coverage:
export GOPATH=`cd ../../..;pwd`;\
export PATH=$$GOPATH/bin:$$PATH;\
export GOFLAGS="$$GOFLAGS -mod=vendor";\
rm -rf ./cov;\
mkdir cov;\
go test -coverpkg=./... -coverprofile=./cov/coverage.data ./cmd/wisdomd \
./common/cpumask ./common/policy ./common/procscan ./common/sched \
./common/sysload ./common/topology ./common/utils;\
go tool cover -html=./cov/coverage.data -o ./cov/coverage.html;\
go tool cover -func=./cov/coverage.data -o ./cov/coverage.txt
install: all
export GOPATH=`cd ../../..;pwd`;\
install -m 700 $$GOPATH/$(PKGPATH)/wisdom $(SBINDIR);\
install -m 700 $$GOPATH/$(PKGPATH)/wisdomd $(SBINDIR)
# wisdom-advisor
## Introduction
Wisdom-advisor is a tunning framework aimming at improving the performance of applications using scheduling or
other methods.
Two policy is supported now in wisdom-advisor:
1. Thread affinity: schedule threads according to their affinity(the affinity can be specified by users or automatic detection).
2. Thread grouping: scheduling threads according to what they are doing.
#### 介绍
Wisdom-advisor is a tunning framework aimming at improving the performance of applications.
There are several functions optinal that assist scheduling decision like NUMA affinity detection which can
reduce access cross-NUMA memory, net affinity detection which can detect net accessing processes
and get the perferred NUMA node according to the net device they use and more.
#### 软件架构
软件架构说明
Wisdom-advisor now support arm64 architectrue, support for x86 is on the way.
Wisdom-advisor should run with root privileges.
## Build
Please note that go environment is needed and one accessible goproxy server is necessary for Go Modules is used here to manage vendoring packages.
To set available proxy, please refer to [Go Module Proxy](https://proxy.golang.org)
```
mkdir -p $GOPATH/src/gitee.com
cd $GOPATH/src/gitee.com
git clone <wisdom-advisor project>
cd wisdom-advisor
export GO111MODULE=on
go mod vendor
make
```
wisdomd binary file is in $GOPATH/pkg/
#### 安装教程
Run testcases
```
make check
```
## Install
In wisdom-advisor project directory.
```
make install
```
## How to use
Get help infomation
```
wisdomd -h
```
When using thread affinity policy without automatic detection. Wisdomd will get group information from /proc/pid/envrion
and auto set affinity for threads in group. Group environment variable format is as below:
\_\_SCHED\_GROUP\_\<group\_name\>=thread\_name1,thread\_name2...
```
wisdomd --policy threadsaffinity
```
Or we can use automatic detection.
```
wisdomd --policy threadsaffinity --affinityAware
```
When using thread grouping, CPU partition description json script should be provided.
```
wisdomd --policy threadsgrouping --json XXX.json
```
Wisdomd will do some scanning when using threadsaffinity policy with automatic detection and threadsgrouping policy and
this scanning opertation can be shutdown or restart.
```
wisdom --scan start
wisdom --scan stop
```
Other options can be found in help information.
1. xxxx
2. xxxx
3. xxxx
#### 使用说明
1. xxxx
2. xxxx
3. xxxx
#### 参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request
#### 码云特技
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. 码云官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解码云上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是码云最有价值开源项目,是码云综合评定出的优秀开源项目
5. 码云官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. 码云封面人物是一档用来展示码云会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
Note:
For security consideration, the json script that describe CPU partition should be set with appropriate umask.
Normal users should not have the wirte or access permissions.
When not necessary, scan should be stop.
## Licensing
Wisdom is licensed under the Mulan PSL v2.
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
// Package implements a application to set CPU affinity automatic for reduce access cross-NUMA memory
package main
import (
"errors"
"fmt"
"net"
"os"
log "github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
const (
wisdomUsage = `wisdom is to control some part of wisdom
To get more info of how to use wisdom:
# wisdom help
`
)
var version = ""
const cmdSocketPath = "/var/run/wisdom.sock"
func sendCmd(context string, path string) error {
addr, err := net.ResolveUnixAddr("unix", path)
if err != nil {
return err
}
conn, err := net.DialUnix("unix", nil, addr)
if err != nil {
return err
}
if _, err := conn.Write([]byte(context)); err != nil {
return err
}
return nil
}
func runWisdom(ctx *cli.Context) error {
cmd := ctx.String("scan")
if cmd != "start" && cmd != "stop" {
return errors.New("invalid command")
}
if err := sendCmd(fmt.Sprintf("CMD:scan%s", cmd), cmdSocketPath); err != nil {
return err
}
return nil
}
func main() {
app := cli.NewApp()
app.Name = "wisdom"
app.Usage = wisdomUsage
app.Version = version
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "scan",
Value: "",
Usage: "thread feature scan control",
},
}
app.Action = runWisdom
if err := app.Run(os.Args); err != nil {
log.Error(err)
os.Exit(1)
}
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
package main
import (
"bytes"
"errors"
"gitee.com/wisdom-advisor/common/policy"
"gitee.com/wisdom-advisor/common/utils"
log "github.com/sirupsen/logrus"
"net"
"os"
"os/signal"
"strings"
"sync"
"syscall"
)
const sockPerm = 0700
const bufLimit = 128
func commandHandler(conn net.Conn, cmdMap map[string]func(block *policy.ControlBlock), block *policy.ControlBlock) {
buf := make([]byte, 0, bufLimit)
if _, err := conn.Read(buf); err != nil {
conn.Close()
return
}
if bytes.HasPrefix(buf, []byte("CMD:")) {
comm := bytes.TrimPrefix(buf, []byte("CMD:"))
command := strings.Split(string(comm), "\000")
cmd := command[0]
if handler, ok := cmdMap[cmd]; ok {
handler(block)
}
}
conn.Close()
}
func listenUnixSock(path string) (*net.UnixListener, error) {
if utils.IsFileExisted(path) {
return nil, errors.New("Sock file " + path + " already exist")
}
addr, err := net.ResolveUnixAddr("unix", path)
if err != nil {
return nil, errors.New("resolve unix addr fail")
}
listener, err := net.ListenUnix("unix", addr)
if err != nil {
return nil, errors.New("create Unix socket fail")
}
if err := os.Chmod(path, sockPerm); err != nil {
return nil, err
}
return listener, nil
}
func cmdWaitLoop(listener *net.UnixListener, block *policy.ControlBlock, wg *sync.WaitGroup) {
defer wg.Done()
cmdMap := map[string]func(block *policy.ControlBlock){
"scanstart": policy.PtraceScanStart,
"scanstop": policy.PtraceScanEnd,
}
for {
conn, err := listener.Accept()
if err != nil {
log.Info("listener close")
break
}
commandHandler(conn, cmdMap, block)
}
}
func relayQuitSig() chan os.Signal {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1, syscall.SIGUSR2)
return ch
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
// Package implements a application to set CPU affinity automatic for reduce access cross-NUMA memory
package main
import (
"errors"
"log/syslog"
"os"
"sync"
"time"
"gitee.com/wisdom-advisor/common/policy"
"gitee.com/wisdom-advisor/common/ptrace"
"gitee.com/wisdom-advisor/common/utils"
log "github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
const (
wisdomdUsage = `wisdomd daemon is the perf tune daemon
Two policies are supported includes threadsaffinity and threadsgrouping.
CPU partition description json script should be provided if threadsgrouping is specified.
The script is like:
{
"io": [
"0-4",
"93-96"
],
"net": [
"48-92",
"5-47"
]
}
To get more info of how to use wisdomd:
# wisdomd help
`
)
var version = ""
const cmdSocketPath = "/var/run/wisdom.sock"
const (
defaultPeriod = 10
defaultTraceTime = 5
maxPeriod = 3600
)
func setLogLevel(level string) {
switch level {
case "debug":
log.SetLevel(log.DebugLevel)
case "info":
log.SetLevel(log.InfoLevel)
case "warning":
log.SetLevel(log.WarnLevel)
case "error":
log.SetLevel(log.ErrorLevel)
case "fatal":
log.SetLevel(log.FatalLevel)
case "panic":
log.SetLevel(log.PanicLevel)
default:
log.Infof("invalid level %s, default set to info level", level)
}
}
func redirectToSyslog() {
des, e := syslog.New(syslog.LOG_NOTICE, "Wisdomd")
if e == nil {
log.SetOutput(des)
}
}
func doBeforeJob(ctx *cli.Context) error {
if !ctx.Bool("printlog") {
redirectToSyslog()
}
setLogLevel(ctx.String("loglevel"))
period := ctx.Int("period")
if period <= 0 || period > maxPeriod {
log.Errorf("period invalid, should greater than zero and less than %d", maxPeriod)
return errors.New("period invalid")
}
if ctx.String("policy") == "threadsaffinity" {
policy.SwitchNumaAware(ctx.Bool("autonuma"))
policy.SwitchNetAware(ctx.Bool("netaware"))
policy.SwitchCclAware(ctx.Bool("cclaware"))
policy.SwitchCoarseGrain(ctx.Bool("coarsegrain"))
if ctx.Bool("affinityAware") {
tracetime := ctx.Int("tracetime")
if tracetime <= 0 || tracetime >= period {
log.Errorf("tracetime invalid, should greater than zero and less than period %d", period)
return errors.New("tracetime invalid")
}
policy.SetAffinityTraceTime(ctx.Int("tracetime"))
policy.SetAffinityTaskName(ctx.String("task"))
policy.SwitchAffinityAware(ctx.Bool("affinityAware"))
}
} else if ctx.String("policy") == "threadsgrouping" {
tracetime := ctx.Int("tracetime")
if tracetime <= 0 || tracetime >= period {
log.Errorf("tracetime invalid, should greater than zero and less than period %d", period)
return errors.New("tracetime invalid")
}
} else {
return errors.New("invalid policy")
}
err := policy.Init()
return err
}
func threadsAffinityLoop(ctx *cli.Context) error {
var block policy.ControlBlock
var wg sync.WaitGroup
timer := time.NewTicker(time.Duration(ctx.Int("period")) * time.Second)
ch := relayQuitSig()
if ctx.Bool("affinityAware") {
policy.PtraceScanStart(&block)
}
listener, err := listenUnixSock(cmdSocketPath)
if err != nil {
return err
}
wg.Add(1)
go cmdWaitLoop(listener, &block, &wg)
for {
select {
case <-ch:
goto out
case <-timer.C:
policy.BalanceTaskPolicy()
policy.BindTasksPolicy(&block)
}
}
out:
listener.Close()
wg.Wait()
log.Info("quit")
return nil
}
func threadsGroupingLoop(ctx *cli.Context) error {
timer := time.NewTicker(time.Duration(ctx.Int("period")) * time.Second)
ch := relayQuitSig()
var party policy.CPUPartition
var block policy.ControlBlock
var wg sync.WaitGroup
policy.PtraceScanStart(&block)
if ctx.String("json") != "" {
if tmp, err := policy.ParseConfig(ctx.String("json")); err != nil {
return err
} else if len(tmp.Groups) == 0 {
return errors.New("fail to get vaild partition")
} else {
party = tmp
}
} else {
log.Info("use default partition")
party = policy.GenerateDefaultPartitions()
}
listener, err := listenUnixSock(cmdSocketPath)
if err != nil {
return err
}
wg.Add(1)
go cmdWaitLoop(listener, &block, &wg)
for {
select {
case <-ch:
goto out
case <-timer.C:
if !policy.ShouldStartPtraceScan(&block) {
continue
}
if pid, err := utils.GetPid(ctx.String("task")); err == nil {
log.Infof("target pid %d", pid)
threads, err := ptrace.DoCollect(pid, ctx.Int("tracetime"), ptrace.ParseSyscall)
if err != nil {
log.Error(err)
}
policy.BindPartition(party, threads, policy.IONetBindPolicy)
}
}
}
out:
listener.Close()
wg.Wait()
log.Info("quit")
return nil
}
func runWisdomd(ctx *cli.Context) error {
if ctx.String("policy") == "threadsaffinity" {
return threadsAffinityLoop(ctx)
} else if ctx.String("policy") == "threadsgrouping" {
return threadsGroupingLoop(ctx)
}
return nil
}
func main() {
app := cli.NewApp()
app.Name = "wisdomd"
app.Usage = wisdomdUsage
app.Version = version
app.Flags = []cli.Flag{
cli.BoolFlag{
Name: "autonuma",
Usage: "turn on numa aware schedule",
},
cli.BoolFlag{
Name: "cclaware",
Usage: "bind thread group inside same cluster",
},
cli.BoolFlag{
Name: "coarsegrain",
Usage: "bind thread in coarse grain",
},
cli.BoolFlag{
Name: "printlog",
Usage: "output log to terminal for debugging",
},
cli.StringFlag{
Name: "loglevel",
Value: "info",
Usage: "log level",
},
cli.IntFlag{
Name: "period",
Value: defaultPeriod,
Usage: "scan and balance period",
},
cli.BoolFlag{
Name: "affinityAware",
Usage: "enable thread affinity Aware",
},
cli.StringFlag{
Name: "task",
Value: "",
Usage: "the name of the task which needs to be affinity aware",
},
cli.Uint64Flag{
Name: "tracetime",
Value: defaultTraceTime,
Usage: "time of tracing",
},
cli.BoolFlag{
Name: "netaware",
Usage: "enable net affinity Aware",
},
cli.StringFlag{
Name: "policy",
Value: "threadsaffinity",
Usage: "specify policy which can be threadsaffinity or threadsgrouping",
},
cli.StringFlag{
Name: "json",
Value: "",
Usage: "CPU partition description script",
},
}
app.Before = doBeforeJob
app.Action = runWisdomd
if err := app.Run(os.Args); err != nil {
log.Error(err)
os.Exit(1)
}
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
package main
import (
"flag"
"fmt"
"os"
"testing"
"time"
"gitee.com/wisdom-advisor/common/policy"
"gitee.com/wisdom-advisor/common/testlib"
log "github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
const testWaitDur = 10
const testThreadNum = 4
const validPeriod = 10
const invalidPeriod1 = 3700
const invalidPeriod2 = -1
// TestFullProcedure is to test the whole procedure
func TestFullProcedure(t *testing.T) {
_, _, taskStub := testlib.InitStub()
tids := taskStub.CreateTasks(testThreadNum)
os.Args = os.Args[:1]
os.Args = append(os.Args, "--policy")
os.Args = append(os.Args, "threadsaffinity")
os.Args = append(os.Args, "--cclaware")
os.Args = append(os.Args, "--loglevel")
os.Args = append(os.Args, "info")
os.Args = append(os.Args, "--period")
os.Args = append(os.Args, "10")
os.Args = append(os.Args, "--printlog")
go main()
time.Sleep(time.Duration(testWaitDur+1) * time.Second)
for _, tid := range tids {
if _, err := testlib.GetAffinityStub(tid); err != nil {
t.Errorf("tid %d set affinity failed\n", tid)
}
}
for _, tid := range tids {
policy.UnbindTaskPolicy(tid)
}
testlib.CleanStub()
os.Remove(cmdSocketPath)
}
func createCtxFromFlag(flag *flag.FlagSet) *cli.Context {
app := cli.NewApp()
ctx := cli.NewContext(app, flag, nil)
return ctx
}
// TestSetLogLevel test --loglevel argument
func TestSetLogLevel(t *testing.T) {
type testLevelData struct {
setLevel string
expectLevel log.Level
}
data := []testLevelData{
{string("invalid"), log.InfoLevel},
{string("debug"), log.DebugLevel},
{string("info"), log.InfoLevel},
{string("warning"), log.WarnLevel},
{string("error"), log.ErrorLevel},
{string("fatal"), log.FatalLevel},
{string("panic"), log.PanicLevel},
}
for _, d := range data {
set := flag.NewFlagSet("", 0)
set.String("policy", "threadsaffinity", "policy")
policyData := []string{"--policy", "threadsaffinity"}
set.String("loglevel", "info", "log level")
loglevelData := []string{"--loglevel", d.setLevel}
// shut up error where period is not set
set.Int("period", defaultPeriod, "scan and balance period")
periodData := []string{"--period", fmt.Sprintf("%d", validPeriod)}
data := append(loglevelData, periodData...)
data = append(data, policyData...)
set.Parse(data)
ctx := createCtxFromFlag(set)
if err := doBeforeJob(ctx); err != nil {
t.Errorf("set loglevel %s failed\n", d.setLevel)
}
if log.GetLevel() != d.expectLevel {
t.Errorf("test level %s, expect level %d, actual %d\n", d.setLevel, d.expectLevel, log.GetLevel())
}
}
}
func testPeriod(p int, t int, policy string) error {
set := flag.NewFlagSet("", 0)
set.Int("period", defaultPeriod, "scan and balance period")
data := []string{"--period", fmt.Sprintf("%d", p)}
set.String("policy", "threadsaffinity", "policy")
policyData := []string{"--policy", policy}
set.Int("tracetime", defaultPeriod, "tracetime")
traceTime := []string{"--tracetime", fmt.Sprintf("%d", t)}
set.Bool("affinityAware", false, "affinityAware")
affinityAware := []string{"--affinityAware"}
data = append(data, policyData...)
data = append(data, traceTime...)
data = append(data, affinityAware...)
set.Parse(data)
ctx := createCtxFromFlag(set)
return doBeforeJob(ctx)
}
func testSetPeriod(t *testing.T, policy string) {
invalidData := []int{invalidPeriod1, invalidPeriod2, 0}
for _, p := range invalidData {
err := testPeriod(p, 1, "threadsaffinity")
if err == nil {
t.Errorf("expect error with period %d\n", p)
}
}
if err := testPeriod(validPeriod, 1, "threadsaffinity"); err != nil {
t.Errorf("set period %d return err %s\n", validPeriod, err.Error())
}
for _, p := range invalidData {
err := testPeriod(p, 1, "threadsaffinity")
if err == nil {
t.Errorf("expect error with period %d\n", p)
}
}
if err := testPeriod(validPeriod, 1, "threadsaffinity"); err != nil {
t.Errorf("set period %d return err %s\n", validPeriod, err.Error())
}
}
// TestSetPeriod test --period argument
func TestSetPeriod(t *testing.T) {
testSetPeriod(t, "threadsaffinity")
testSetPeriod(t, "threadsgrouping")
}
func testSetTraceTime(t *testing.T, policy string) {
if err := testPeriod(validPeriod, 0, policy); err == nil {
t.Errorf("expect error with tracetime\n")
}
if err := testPeriod(validPeriod, -1, policy); err == nil {
t.Errorf("expect error with tracetime\n")
}
if err := testPeriod(validPeriod, validPeriod+1, policy); err == nil {
t.Errorf("expect error with tracetime\n")
}
if err := testPeriod(validPeriod, 1, policy); err != nil {
t.Errorf("set period %d return err %s\n", validPeriod, err.Error())
}
}
// TestSetTraceTime --tracetime argument
func TestSetTraceTime(t *testing.T) {
testSetTraceTime(t, "threadsaffinity")
testSetTraceTime(t, "threadsgrouping")
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
// Package cpumask implements utility to represent cpumask
package cpumask
import (
"gitee.com/wisdom-advisor/common/utils"
"strconv"
)
const (
cpumaskNum = 10
oneMaskBits = 64
)
// Cpumask is union bit of cpus
type Cpumask struct {
Masks [cpumaskNum]uint64
}
// Init cpumask
func (mask *Cpumask) Init() {
for i := 0; i < cpumaskNum; i++ {
mask.Masks[i] = 0
}
}
// And bits of mask with dst
func (mask *Cpumask) And(dst *Cpumask) {
for i := 0; i < cpumaskNum; i++ {
mask.Masks[i] &= dst.Masks[i]
}
}
// Or bits of mask with dst
func (mask *Cpumask) Or(dst *Cpumask) {
for i := 0; i < cpumaskNum; i++ {
mask.Masks[i] |= dst.Masks[i]
}
}
// IsEqual check if bits of mask is equal to bits of dst
func (mask *Cpumask) IsEqual(dst *Cpumask) bool {
for i := 0; i < cpumaskNum; i++ {
if mask.Masks[i] != dst.Masks[i] {
return false
}
}
return true
}
// Set correspond bit of mask
func (mask *Cpumask) Set(nbit int) {
maskIndex := nbit / oneMaskBits
var oneMask = uint64(1) << uint(nbit%oneMaskBits)
if maskIndex >= cpumaskNum || maskIndex < 0 {
return
}
mask.Masks[maskIndex] |= oneMask
}
// Clear correspond bit of mask
func (mask *Cpumask) Clear(nbit int) {
maskIndex := nbit / oneMaskBits
var oneMask = uint64(1) << uint(nbit%oneMaskBits)
if maskIndex >= cpumaskNum || maskIndex < 0 {
return
}
mask.Masks[maskIndex] &= ^oneMask
}
// Copy bits of mask to dst
func (mask *Cpumask) Copy(dst *Cpumask) {
for i := 0; i < cpumaskNum; i++ {
mask.Masks[i] = dst.Masks[i]
}
}
// ParseString get mask bits from hex string like "fff00aaff"
func (mask *Cpumask) ParseString(str string) error {
var err error
const CharsPerUint64 = 16
mask.Init()
for maskIndex := 0; maskIndex < cpumaskNum; maskIndex++ {
endIndex := len(str) - maskIndex*CharsPerUint64
startIndex := endIndex - CharsPerUint64
if startIndex < 0 {
startIndex = 0
}
oneMaskStr := str[startIndex:endIndex]
mask.Masks[maskIndex], err = strconv.ParseUint(oneMaskStr, utils.HexBase, utils.Uint64Bits)
if err != nil {
return err
}
if startIndex == 0 {
break
}
}
return nil
}
func oneMaskWeight(mask uint64) int {
weight := 0
for mask > 0 {
weight++
mask = mask & (mask - 1)
}
return weight
}
// Weight get mask bits count
func (mask *Cpumask) Weight() int {
weight := 0
for i := 0; i < cpumaskNum; i++ {
weight += oneMaskWeight(mask.Masks[i])
}
return weight
}
func lowBit(mask uint64) int {
for i := 0; i < oneMaskBits; i++ {
if mask&(1<<i) != 0 {
return i
}
}
return -1
}
// Foreach get next set bit of mask from startBit
func (mask *Cpumask) Foreach(startBit int) int {
startIndex := (startBit + 1) / oneMaskBits
firstMask := mask.Masks[startIndex]
firstMask &= ^((uint64(1) << uint64((startBit+1)%oneMaskBits)) - 1)
leftbit := lowBit(firstMask)
if leftbit != -1 {
return startIndex*oneMaskBits + leftbit
}
for startIndex++; startIndex < cpumaskNum; startIndex++ {
leftbit = lowBit(mask.Masks[startIndex])
if leftbit != -1 {
return startIndex*oneMaskBits + leftbit
}
}
return -1
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
package netaffinity
import (
"errors"
"fmt"
"gitee.com/wisdom-advisor/common/utils"
log "github.com/sirupsen/logrus"
"os"
"regexp"
"strconv"
"syscall"
)
// GetInode can get the inode num of one file
func GetInode(path string) (uint64, error) {
var stat syscall.Stat_t
file, err := os.Open(path)
if err != nil {
return 0, errors.New("open Inode path fail")
}
defer file.Close()
if err := syscall.Fstat(int(file.Fd()), &stat); err != nil {
return 0, err
}
return stat.Ino, nil
}
// GetPidNSInode can get the inode of the net namespace of specified process
func GetPidNSInode(pid uint64) (uint64, error) {
reg := regexp.MustCompile(`net:\[(\d+)\]`)
link, err := os.Readlink(fmt.Sprintf("%s/%d/ns/net", ProcDir, pid))
if err != nil {
return 0, errors.New("readlink fail")
}
params := reg.FindStringSubmatch(link)
if params == nil {
return 0, errors.New("get ns fail")
}
ns, err := strconv.ParseUint(params[1], utils.DecimalBase, utils.Uint64Bits)
if err != nil {
return 0, err
}
return ns, nil
}
// Setns can the namespace of current process
func Setns(fd uintptr, flags uintptr) error {
if _, _, err := syscall.RawSyscall(syscall.SYS_SETNS, fd, flags, 0); err != 0 {
return errors.New("setns fail")
}
return nil
}
// NSEnter can enter the same net namespace of one specified process
func NSEnter(pid uint64) error {
file, err := os.Open(fmt.Sprintf("%s/%d/ns/net", ProcDir, pid))
if err != nil {
log.Debugf("open pid %d ns file failed\n", pid)
return err
}
defer file.Close()
if err := Setns(file.Fd(), 0); err != nil {
return err
}
return nil
}
// SetRootNs can return to the root namespace
func SetRootNs() error {
return NSEnter(1)
}
// RemountSysfs can remount sysfs
func RemountSysfs() error {
if err := syscall.Unmount("/sys", syscall.MNT_DETACH); err != nil {
return err
}
if err := syscall.Mount("sysfs", "/sys", "sysfs", 0, ""); err != nil {
return err
}
return nil
}
// MountSysfs can mount sysfs to specified path
func MountSysfs(path string) error {
if err := syscall.Mount("sysfs", path, "sysfs", 0, ""); err != nil {
return err
}
return nil
}
// UmountSysfs do the same as unmount
func UmountSysfs(path string) error {
if err := syscall.Unmount(path, syscall.MNT_DETACH); err != nil {
return err
}
return nil
}
// RemountNewSysfs remount sysfs to with specified path
func RemountNewSysfs(path string) error {
if err := syscall.Unmount(path, syscall.MNT_DETACH); err != nil {
return err
}
if err := syscall.Mount("sysfs", path, "sysfs", 0, ""); err != nil {
return err
}
return nil
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
// Package netaffinity provides functions for net affinity detection
package netaffinity
import (
"bufio"
"errors"
"fmt"
"gitee.com/wisdom-advisor/common/cpumask"
"gitee.com/wisdom-advisor/common/utils"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"net"
"os"
"regexp"
"strconv"
"strings"
)
const sysfsPerm = 0700
const (
hexStrLenIPv4 = 8
AssumedDevNum = 10
AssumedNodeNum = 4
AssumedCPUNum = 96
)
// ProcDir is the path of proc sysfs
var ProcDir = "/proc/"
// NetDevDir is the path of net device sysfs
var NetDevDir = "/sys/class/net/"
// NetInterface is to describe one net device
type NetInterface struct {
IrqNode *[]int
Name string
PCINode int
}
// NSIPDevCache is cache for IP inode pairs
type NSIPDevCache struct {
Cache *map[string]string
NSName string
NSInode uint64
}
func getProcSocketInode(tid uint64) ([]uint64, error) {
var inodes []uint64
path := fmt.Sprintf("%s%d/fd/", ProcDir, tid)
reg := regexp.MustCompile(`socket:\[(\d+)\]`)
files, err := ioutil.ReadDir(path)
if err != nil {
return inodes, errors.New("read fd dir fail")
}
for _, file := range files {
link, err := os.Readlink(path + file.Name())
if err != nil {
log.Debug("Socket Readlink fail")
continue
}
params := reg.FindStringSubmatch(link)
if params != nil {
inode, err := strconv.ParseUint(params[1], utils.DecimalBase, utils.Uint64Bits)
if err == nil {
inodes = append(inodes, inode)
}
}
}
return inodes, nil
}
// CreateDevHashCache is to get cache that decribe net device existed
func CreateDevHashCache() (*map[string]string, error) {
cache := make(map[string]string, AssumedDevNum)
interfaces, err := net.Interfaces()
if err != nil {
return nil, err
}
for _, dev := range interfaces {
addrs, _ := dev.Addrs()
if len(addrs) > 0 {
ip, _, err := net.ParseCIDR(addrs[0].String())
if err != nil {
log.Error(err)
continue
}
cache[ip.String()] = dev.Name
}
}
return &cache, nil
}
func transNetOrderHexToIP(hexStr string) (net.IP, error) {
var ret net.IP
var ip [4]byte
if len(hexStr) > hexStrLenIPv4 {
reg := regexp.MustCompile("0000000000000000FFFF0000([A-Za-z0-9]+)")
params := reg.FindStringSubmatch(string(hexStr))
if params != nil {
hexStr = params[1]
} else {
return nil, errors.New("not vaild IPv4 addr")
}
}
for i := 0; i < 4; i++ {
tmp := hexStr[i*2 : i*2+2]
elem, err := strconv.ParseUint(tmp, 16, 8)
if err != nil {
return nil, err
}
ip[3-i] = byte(elem)
}
ret = net.IPv4(ip[0], ip[1], ip[2], ip[3])
return ret, nil
}
// GetInodeIP is to get the related IP of one inode
func GetInodeIP(path string) (map[uint64]net.IP, error) {
ret := make(map[uint64]net.IP, AssumedDevNum)
reg := regexp.MustCompile(
`\s+\S+\s+([A-Za-z0-9]+):\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S+)`)
file, err := os.Open(path)
if err != nil {
return ret, errors.New("open file fail")
}
defer file.Close()
buf := bufio.NewReader(file)
_, _, tag := buf.ReadLine()
if tag == io.EOF {
return ret, nil
}
for {
line, _, tag := buf.ReadLine()
if tag == io.EOF {
break
}
params := reg.FindStringSubmatch(string(line))
if params != nil {
ip, err := transNetOrderHexToIP(params[1])
if err != nil {
log.Debug(err)
continue
}
if ip.IsLoopback() {
continue
}
inode, err := strconv.ParseUint(params[2], utils.DecimalBase, utils.Uint64Bits)
if err != nil {
log.Debug("Get inode error\n")
continue
}
ret[inode] = ip
}
}
return ret, nil
}
func getDevByIP(ip net.IP, cache map[string]string) string {
if dev, ok := cache[ip.String()]; ok {
return dev
}
return ""
}
func isPCIAddr(path string) bool {
reg := regexp.MustCompile(
`[a-zA-Z0-9]{4}:[a-zA-Z0-9]{2}:[a-zA-Z0-9]{2}.[a-zA-Z0-9]{1}`)
params := reg.FindStringSubmatch(path)
return params != nil
}
// GetNetDevPCIPath is to get the PCI path of one net device
func GetNetDevPCIPath(name string) (string, error) {
ret := "/wisysfs/devices/"
link, err := os.Readlink("/wisysfs/class/net/" + name)
if err != nil {
return "", errors.New("readlink fail")
}
reg := regexp.MustCompile(`.*\/devices\/(pci.*)`)
params := reg.FindStringSubmatch(link)
if params == nil {
return "", errors.New("not a PCI device")
}
lines := strings.Split(params[1], "/")
ret = ret + lines[0] + "/"
lines = lines[1:]
for _, line := range lines {
if isPCIAddr(line) {
ret = ret + line + "/"
}
}
return ret, nil
}
// GetNetDevNUMANode is to get the preferred NUMA node of one net device
func GetNetDevNUMANode(name string) (int, error) {
path, err := GetNetDevPCIPath(name)
if err != nil {
return -1, err
}
tmp, err := ioutil.ReadFile(path + "/numa_node")
if err != nil {
return -1, errors.New("read numa_node fail")
}
numa, err := strconv.ParseInt(strings.Replace(string(tmp), "\n", "", -1), utils.DecimalBase, utils.Uint64Bits)
if err != nil {
return -1, err
}
return int(numa), nil
}
func isIrqExisted(irq uint64) bool {
path := fmt.Sprintf("/proc/irq/%d", irq)
return utils.IsFileExisted(path)
}
func getNetDevIrq(dev string) ([]uint64, error) {
var irqs []uint64
path := NetDevDir + dev + "/device/msi_irqs/"
if utils.IsFileExisted(path) {
files, err := ioutil.ReadDir(path)
if err != nil {
return irqs, errors.New("read irq dir fail")
}
for _, file := range files {
irq, err := strconv.ParseUint(file.Name(), utils.DecimalBase, utils.Uint64Bits)
if err != nil {
continue
}
if isIrqExisted(irq) {
irqs = append(irqs, irq)
}
}
}
return irqs, nil
}
func getIrqCPUAffinity(irq uint64) ([]int, error) {
var cpus []int
var mask cpumask.Cpumask
if !isIrqExisted(irq) {
return cpus, errors.New("irq not exists")
}
path := fmt.Sprintf("%s/irq/%d/effective_affinity", ProcDir, irq)
data, err := ioutil.ReadFile(path)
if err != nil {
return cpus, errors.New("read irq affinity fail")
}
affini := strings.Replace(string(data), ",", "", -1)
affini = strings.Replace(affini, "\n", "", -1)
mask.ParseString(affini)
for cpu := mask.Foreach(-1); cpu != -1; cpu = mask.Foreach(cpu) {
cpus = append(cpus, cpu)
}
return cpus, nil
}
func getDevIrqNode(dev string) (*[]int, error) {
var nodes []int
cpuMap := make(map[int]int, AssumedCPUNum)
nodeMap := make(map[int]int, AssumedNodeNum)
irqs, err := getNetDevIrq(dev)
if err != nil {
return nil, err
}
for _, irq := range irqs {
cpus, err := getIrqCPUAffinity(irq)
if err != nil {
log.Info(err)
continue
}
for _, cpu := range cpus {
cpuMap[cpu] = cpu
}
}
for key := range cpuMap {
node := utils.GetCPUNumaID(key)
nodeMap[node] = node
}
for key := range nodeMap {
nodes = append(nodes, key)
}
return &nodes, nil
}
func getDevRelated(pid uint64, cache map[string]string) ([]string, error) {
devMap := make(map[string]int, AssumedDevNum)
var dev []string
pathes := []string{"/net/tcp", "/net/tcp6", "/net/udp", "/net/udp6"}
inodes, err := getProcSocketInode(pid)
if err != nil {
return dev, err
}
file, err := os.Open(fmt.Sprintf("%s/%d/ns/net", ProcDir, pid))
if err != nil {
return dev, errors.New("open ns file fail")
}
defer file.Close()
for _, inode := range inodes {
for _, path := range pathes {
inodeMap, err := GetInodeIP(ProcDir + path)
if err != nil {
log.Info(err)
continue
}
if ip, ok := inodeMap[inode]; ok {
tmp := getDevByIP(ip, cache)
if tmp != "" {
devMap[tmp] = 1
break
}
}
}
}
for key := range devMap {
dev = append(dev, key)
}
return dev, nil
}
func getDevCache(pid uint64) (*NSIPDevCache, error) {
var cache NSIPDevCache
var err error
if cache.NSInode, err = GetPidNSInode(pid); err != nil {
return nil, err
}
if cache.Cache, err = CreateDevHashCache(); err != nil {
return nil, err
}
return &cache, nil
}
func getDevIndex(dev string) (int, error) {
data, err := ioutil.ReadFile("/wisysfs/class/net/" + dev + "/ifindex")
if err != nil {
return -1, errors.New("open ifindex fail")
}
index, err := strconv.ParseInt(strings.Replace(string(data), "\n", "", -1),
utils.DecimalBase, utils.Uint64Bits)
if err != nil {
return -1, err
}
return int(index), nil
}
func getDevLink(dev string) (int, error) {
data, err := ioutil.ReadFile("/wisysfs/class/net/" + dev + "/iflink")
if err != nil {
return -1, errors.New("open iflink fail")
}
link, err := strconv.ParseInt(strings.Replace(string(data), "\n", "", -1),
utils.DecimalBase, utils.Uint64Bits)
if err != nil {
return -1, err
}
return int(link), nil
}
func getIflink(dev string) int {
index, err := getDevIndex(dev)
if err != nil {
log.Debug(err)
return -1
}
link, err := getDevLink(dev)
if err != nil {
log.Debug(err)
return -1
}
if link != index {
return link
}
return -1
}
func getNetifInfo(name string) NetInterface {
var dev NetInterface
var err error
dev.Name = name
dev.IrqNode, err = getDevIrqNode(name)
if err != nil {
log.Info(err)
}
dev.PCINode, err = GetNetDevNUMANode(name)
if err != nil {
log.Info(err)
}
return dev
}
func getDevByIndex(index int) string {
files, err := ioutil.ReadDir(NetDevDir)
if err != nil {
log.Debug("Read dir fail\n")
return ""
}
for _, file := range files {
data, err := ioutil.ReadFile(NetDevDir + file.Name() + "/ifindex")
if err != nil {
log.Debug("Get ifindex fail")
continue
}
ifindex, err := strconv.ParseInt(strings.Replace(string(data), "\n", "", -1),
utils.DecimalBase, utils.Uint64Bits)
if err != nil {
log.Debug("Get ifindex fail")
continue
}
if int(ifindex) == index {
return file.Name()
}
}
return ""
}
func isBondingDev(dev string) bool {
return utils.IsFileExisted(NetDevDir + dev + "/bonding")
}
func getBondSlaves(dev string) []string {
var devs []string
tmp, err := ioutil.ReadFile(NetDevDir + dev + "/bonding/slaves")
if err != nil {
log.Debug("Get Slaves fail")
return devs
}
return strings.Split(strings.Replace(string(tmp), "\n", "", -1), " ")
}
func preDetect() error {
if err := os.Mkdir("/wisysfs", sysfsPerm); err != nil {
return errors.New("make dir fail")
}
if err := MountSysfs("/wisysfs"); err != nil {
return errors.New("mount dir fail")
}
return nil
}
func postDetect() error {
if err := UmountSysfs("/wisysfs"); err != nil {
return errors.New("umount sysfs fail")
}
if err := os.RemoveAll("/wisysfs"); err != nil {
return errors.New("clean sysfs fail")
}
return nil
}
func detectDevRelated(pid uint64) ([]NetInterface, error) {
if err := preDetect(); err != nil {
return nil, err
}
ret, err := GetRealDevRelated(pid)
if err != nil {
return nil, err
}
if err = postDetect(); err != nil {
log.Error(err)
}
return ret, err
}
// GetRealDevRelated is to get the related net device of one process
func GetRealDevRelated(pid uint64) ([]NetInterface, error) {
var devInfo []NetInterface
var linkArr []int
var hostDevs Queue
NSEnter(pid)
if err := RemountNewSysfs("/wisysfs"); err != nil {
SetRootNs()
return devInfo, err
}
cache, err := getDevCache(pid)
if err != nil {
SetRootNs()
return devInfo, err
}
devs, err := getDevRelated(pid, *(cache.Cache))
if err != nil {
SetRootNs()
return devInfo, err
}
for _, dev := range devs {
if iflink := getIflink(dev); iflink != -1 {
linkArr = append(linkArr, iflink)
} else {
devInfo = append(devInfo, getNetifInfo(dev))
}
}
SetRootNs()
if err := RemountNewSysfs("/wisysfs"); err != nil {
return devInfo, err
}
for _, link := range linkArr {
dev := getDevByIndex(link)
if dev == "" {
log.Debug("Fail to get link dev\n")
} else {
hostDevs.PushBack(dev)
}
}
for {
if hostDevs.IsEmpty() {
break
}
dev, _ := hostDevs.PopFront()
if iflink := getIflink(dev.(string)); iflink != -1 {
hostDevs.PushBack(getDevByIndex(iflink))
} else if isBondingDev(dev.(string)) {
slaves := getBondSlaves(dev.(string))
for _, slave := range slaves {
hostDevs.PushBack(slave)
}
} else {
devInfo = append(devInfo, getNetifInfo(dev.(string)))
}
}
return devInfo, nil
}
// GetProcessNetNuma is to get the preferred NUMA node of one process according to net access
func GetProcessNetNuma(pid uint64) (int, error) {
var numaNode int
var SecNode int
nodes := make(map[int]int, AssumedNodeNum)
nodesBak := make(map[int]int, AssumedNodeNum)
devs, err := detectDevRelated(pid)
if err != nil {
return -1, err
}
log.Debugf("%d related numa\n", pid)
for _, dev := range devs {
log.Debugf(" %s\n", dev.Name)
for _, irqNode := range *(dev.IrqNode) {
nodes[irqNode] = 1
log.Debugf(" irqnode: %d\n", irqNode)
numaNode = irqNode
}
if dev.PCINode > 0 {
nodesBak[dev.PCINode] = dev.PCINode
SecNode = dev.PCINode
}
}
if len(nodes) != 1 {
log.Info("Net dev IRQs on multi numa nodes or unable to handle irq info\n")
if len(nodesBak) == 1 {
return SecNode, nil
}
return -1, nil
}
return numaNode, nil
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
package netaffinity
import (
"fmt"
"gitee.com/wisdom-advisor/common/utils"
"io/ioutil"
"strconv"
"strings"
"testing"
)
// TestAllGetDevRelated test GetDevRelated func
func TestAllGetDevRelated(t *testing.T) {
var sshdNum int
var resNum int
sshdNum = 0
resNum = 0
files, err := ioutil.ReadDir(ProcDir)
if err != nil {
return
}
for _, file := range files {
if pid, err := strconv.ParseUint(file.Name(), utils.DecimalBase, utils.Uint64Bits); err != nil {
continue
} else {
devs, err := detectDevRelated(uint64(pid))
if err != nil {
fmt.Print(err)
continue
}
if len(devs) == 0 {
continue
}
tmp, err := ioutil.ReadFile(ProcDir + file.Name() + "/cmdline")
if err != nil {
fmt.Printf("Get cmdline fail\n")
continue
}
comm, err := ioutil.ReadFile(ProcDir + file.Name() + "/comm")
if err != nil {
fmt.Printf("Get cmdline fail\n")
continue
}
if string(comm) == "sshd" {
sshdNum = sshdNum + 1
}
fmt.Printf("%s: %s\n", file.Name(), strings.Replace(string(tmp), "\n", "", -1))
for _, dev := range devs {
fmt.Printf(" |%s---node:%d\n", dev.Name, dev.PCINode)
resNum = resNum + 1
}
}
}
if sshdNum > 1 && resNum == 0 {
t.Errorf("bind task early\n")
}
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use que software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
package netaffinity
import "errors"
// Queue is generic queue implementation
type Queue struct {
queue []interface{}
}
// PushBack is generic implementation
func (que *Queue) PushBack(node interface{}) {
que.queue = append(que.queue, node)
}
// PopFront is generic implementation
func (que *Queue) PopFront() (interface{}, error) {
if que.Size() == 0 {
return nil, errors.New("pop empty queue")
}
ret := que.queue[0]
que.queue = que.queue[1:]
return ret, nil
}
// Size returns the size of the queue
func (que *Queue) Size() int {
return len(que.queue)
}
// IsEmpty indicates whether the queue is empty
func (que *Queue) IsEmpty() bool {
return que.Size() <= 0
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
// Package policy provides methods to set affinity of tasks
package policy
import (
"bytes"
"encoding/json"
"errors"
"gitee.com/wisdom-advisor/common/ptrace"
"gitee.com/wisdom-advisor/common/sched"
"gitee.com/wisdom-advisor/common/utils"
log "github.com/sirupsen/logrus"
"os"
"regexp"
"runtime"
"strconv"
)
const (
limitEnvSize = 1048576
maxCPUNum = 2048
AssmuedNodeNum = 4
AssmuedSysCallClassNum = 5
)
const (
ioAccess = 0
netAccess = 1
)
// CPUGroup describe one CPU group
type CPUGroup struct {
Flag int
CPUs []int
}
// CPUPartition decribe the CPU partition
type CPUPartition struct {
FlagCount map[int]int
Groups []CPUGroup
}
// PartitionCreateGroup is to create one CPU group in the partition
func PartitionCreateGroup(part *CPUPartition, CPUs []int, flag int) {
var group CPUGroup
group.CPUs = append(group.CPUs, CPUs...)
group.Flag = flag
PartitionAddGroup(part, group)
}
// PartitionAddGroup is to add one group to the partition
func PartitionAddGroup(part *CPUPartition, group CPUGroup) {
part.Groups = append(part.Groups, group)
if _, ok := part.FlagCount[group.Flag]; !ok {
part.FlagCount[group.Flag] = 1
} else {
part.FlagCount[group.Flag] = part.FlagCount[group.Flag] + 1
}
}
// BindPartition is to bind the threads to the partition according to specific policy
func BindPartition(party CPUPartition, threads []*ptrace.ProcessFeature,
handler func(party CPUPartition, process []*ptrace.ProcessFeature)) {
handler(party, threads)
}
// IONetBindPolicy is the thread grouping policy according to IO and net access
func IONetBindPolicy(party CPUPartition, threads []*ptrace.ProcessFeature) {
var netThreads []*ptrace.ProcessFeature
var IOThreads []*ptrace.ProcessFeature
var netCPUs []CPUGroup
var IOCPUs []CPUGroup
for _, thread := range threads {
if thread.SysCount.NetAccess > 0 {
netThreads = append(netThreads, thread)
}
if thread.SysCount.IOGetEvents > 0 {
IOThreads = append(IOThreads, thread)
}
}
for _, group := range party.Groups {
if group.Flag == ioAccess {
IOCPUs = append(IOCPUs, group)
}
if group.Flag == netAccess {
netCPUs = append(netCPUs, group)
}
}
bindThreadsToGroups(IOCPUs, IOThreads)
bindThreadsToGroups(netCPUs, netThreads)
}
func bindThreadsToGroups(CPUset []CPUGroup, threads []*ptrace.ProcessFeature) {
var base int
if len(threads) == 0 {
return
}
for _, set := range CPUset {
base = base + len(set.CPUs)
}
if base == 0 {
return
}
for _, set := range CPUset {
round := (len(threads)*len(set.CPUs) + len(set.CPUs)) / base
if round >= len(threads) {
round = len(threads)
bindThreadsToGroup(set, threads[0:round])
return
}
bindThreadsToGroup(set, threads[0:round])
threads = threads[round:]
base = base - len(set.CPUs)
if base < 0 {
return
}
}
}
func bindThreadsToGroup(CPUset CPUGroup, threads []*ptrace.ProcessFeature) {
for _, thread := range threads {
log.Info("bind ", thread.Pid, " to cpu ", CPUset.CPUs)
if err := sched.SetAffinity(thread.Pid, CPUset.CPUs); err != nil {
log.Info("bind error")
}
}
}
// PartitionInfo define the format of json script
type PartitionInfo struct {
Io []string `json:"io"`
Net []string `json:"net"`
}
func stringToInts(ints string) ([]int, error) {
var ret []int
reg := regexp.MustCompile(`\s*(\d+)\s*-\s*(\d+)\s*`)
params := reg.FindStringSubmatch(ints)
if params != nil {
floor, err := strconv.ParseUint(params[1], utils.DecimalBase, utils.Uint64Bits)
if err != nil {
return ret, errors.New("wrong CPU num")
}
ceil, err := strconv.ParseUint(params[2], utils.DecimalBase, utils.Uint64Bits)
if err != nil {
return ret, errors.New("wrong CPU num")
}
if floor > ceil || ceil > maxCPUNum {
return ret, errors.New("wrong CPU num")
}
for i := floor; i <= ceil; i++ {
ret = append(ret, int(i))
}
} else {
cpu, err := strconv.ParseUint(ints, utils.DecimalBase, utils.Uint64Bits)
if err != nil {
return ret, errors.New("wrong CPU num")
}
ret = append(ret, int(cpu))
}
return ret, nil
}
// ParseConfig is to parse the json script to get the CPU partition
func ParseConfig(path string) (CPUPartition, error) {
var party CPUPartition
var info PartitionInfo
party.FlagCount = make(map[int]int, AssmuedSysCallClassNum)
file, err := os.Open(path)
if err != nil {
return party, errors.New("open config fail")
}
defer file.Close()
buf := bytes.NewBuffer(make([]byte, 0, limitEnvSize))
_, err = buf.ReadFrom(file)
if err != nil {
return party, errors.New("read config fail")
}
if err := json.Unmarshal(buf.Bytes(), &info); err != nil {
return party, err
}
for _, set := range info.Io {
cpus, err := stringToInts(set)
if err != nil {
return party, err
}
PartitionCreateGroup(&party, cpus, ioAccess)
}
for _, set := range info.Net {
cpus, err := stringToInts(set)
if err != nil {
return party, err
}
PartitionCreateGroup(&party, cpus, netAccess)
}
return party, nil
}
// GenerateDefaultPartitions is to generate default partitions
func GenerateDefaultPartitions() CPUPartition {
var party CPUPartition
party.FlagCount = make(map[int]int, AssmuedSysCallClassNum)
numaMap := make(map[int]*CPUGroup, AssmuedNodeNum)
for i := 0; i < runtime.NumCPU(); i++ {
numaID := utils.GetCPUNumaID(i)
if _, ok := numaMap[numaID]; !ok {
numaMap[numaID] = new(CPUGroup)
numaMap[numaID].Flag = netAccess
}
numaMap[numaID].CPUs = append(numaMap[numaID].CPUs, i)
}
for _, group := range numaMap {
PartitionAddGroup(&party, *group)
}
return party
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
package policy
import (
"fmt"
"gitee.com/wisdom-advisor/common/ptrace"
"gitee.com/wisdom-advisor/common/sched"
"io/ioutil"
"math/rand"
"os"
"testing"
"time"
)
const defaultPerm = 0644
const (
testSysCount = 2
pidStep = 1000
testGroupsNum = 4
testIOCPUNum = 4
testNetCPUNum = 44
)
// TestBindPartition test BindPartition func
func TestBindPartition(t *testing.T) {
var part CPUPartition
var threads []*ptrace.ProcessFeature
var io []int
var ioNext []int
var net []int
var netNext []int
sched.SetAffinity = setThreadAffinity
part.FlagCount = make(map[int]int)
for i := 0; i < 257; i++ {
var thread ptrace.ProcessFeature
thread.Pid = uint64(i)
thread.SysCount.NetAccess = testSysCount
threads = append(threads, &thread)
}
for i := 0; i < 57; i++ {
var thread ptrace.ProcessFeature
thread.Pid = uint64(i + pidStep)
thread.SysCount.IOGetEvents = testSysCount
threads = append(threads, &thread)
}
for i := 0; i < 3; i++ {
io = append(io, i)
}
for i := 93; i < 96; i++ {
ioNext = append(ioNext, i)
}
for i := 3; i < 48; i++ {
net = append(net, i)
}
for i := 48; i < 93; i++ {
netNext = append(netNext, i)
}
PartitionCreateGroup(&part, io, ioAccess)
PartitionCreateGroup(&part, ioNext, ioAccess)
PartitionCreateGroup(&part, net, netAccess)
PartitionCreateGroup(&part, netNext, netAccess)
BindPartition(part, threads, IONetBindPolicy)
}
func setThreadAffinity(tid uint64, cpu []int) error {
return nil
}
// TestParseConfig test ParseConfig
func TestParseConfig(t *testing.T) {
data := []byte("{\n")
data = append(data, []byte(" \"io\": [\n")...)
data = append(data, []byte(" \"0-3\",\n")...)
data = append(data, []byte(" \"92-95\"\n")...)
data = append(data, []byte(" ],\n")...)
data = append(data, []byte(" \"net\": [\n")...)
data = append(data, []byte(" \"48-91\",\n")...)
data = append(data, []byte(" \"4-47\"\n")...)
data = append(data, []byte(" ]\n")...)
data = append(data, []byte("}\n")...)
err := ioutil.WriteFile("./tmp.json", data, defaultPerm)
if err != nil {
t.Errorf("Create fake json fail\n")
}
defer os.Remove("./tmp.json")
party, err := ParseConfig("./tmp.json")
if err != nil {
t.Errorf("Parse json fail")
}
if len(party.Groups) != testGroupsNum {
t.Errorf("Parse json wrong")
}
for _, set := range party.Groups {
if set.Flag == ioAccess && len(set.CPUs) != testIOCPUNum {
t.Errorf("Parse json wrong")
}
if set.Flag == netAccess && len(set.CPUs) != testNetCPUNum {
t.Errorf("Parse json wrong")
}
fmt.Print(set, "\n")
}
}
// TestStringToInts test StringToInts func
func TestStringToInts(t *testing.T) {
rand.Seed(time.Now().Unix())
tmp := rand.Intn(maxCPUNum-1) + 1
if ret, err := stringToInts(fmt.Sprintf("0-%d", tmp)); err != nil {
t.Errorf("parse ints string fail\n")
} else {
for _, cpu := range ret {
if cpu < 0 || cpu > tmp {
t.Errorf("0-9 parse ints string fail\n")
}
}
}
tmp = rand.Intn(maxCPUNum)
if ret, err := stringToInts(fmt.Sprintf("%d", tmp)); err != nil {
t.Errorf("parse ints string fail\n")
} else {
for _, cpu := range ret {
if cpu != tmp {
t.Errorf("0-9 parse ints string fail\n")
}
}
}
}
// TestGenerateDefaultPartitions test GenerateDefaultPartitions func
func TestGenerateDefaultPartitions(t *testing.T) {
party := GenerateDefaultPartitions()
if len(party.Groups) == 0 {
t.Errorf("Didn't generate groups\n")
}
for _, group := range party.Groups {
if len(group.CPUs) == 0 {
t.Errorf("Didn't valid generate groups\n")
}
fmt.Printf("%d:", group.Flag)
fmt.Print(group.CPUs)
}
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
// Package policy provides methods to set affinity of tasks
package policy
import (
"container/list"
"gitee.com/wisdom-advisor/common/netaffinity"
"gitee.com/wisdom-advisor/common/procscan"
"gitee.com/wisdom-advisor/common/sched"
"gitee.com/wisdom-advisor/common/sysload"
"gitee.com/wisdom-advisor/common/threadaffinity"
"gitee.com/wisdom-advisor/common/topology"
"gitee.com/wisdom-advisor/common/utils"
log "github.com/sirupsen/logrus"
"sync/atomic"
)
type bindGroupInfo struct {
numaNode *topology.TopoNode
bindNode *topology.TopoNode
name string
tasks list.List
}
// ControlBlock is to control the behaviour of policy
type ControlBlock struct {
ptraceScanSwitch int32
}
type bindTaskInfo struct {
group *bindGroupInfo
groupEle *list.Element
nodeEle *list.Element
retryEle *list.Element
oldBindNode *topology.TopoNode
newBindNode *topology.TopoNode
tid uint64
}
var bindTaskMap = make(map[uint64]*bindTaskInfo)
var bindGroupMap = make(map[string]*bindGroupInfo)
var retryTasks list.List
var netAwareOn = false
var numaAwareOn = false
var cclAwareOn = false
var coarseGrain = false
const (
cpuGapPct = 30
toPercentage = 100
eventsBufferLen = 5000
)
var affinityAware = false
var taskName string
var traceTime int
var sysLoad sysload.SystemLoad
var scanHandler procscan.SchedGroupHandler
var delayTaskCh = make(chan uint64, eventsBufferLen)
func isThreadBind(tid uint64) bool {
_, ok := bindTaskMap[tid]
return ok
}
func makeBindGroupByNumaID(numaID int) *bindGroupInfo {
var bindGroup bindGroupInfo
bindGroup.numaNode = topology.GetNumaNodeByID(numaID)
bindGroup.tasks.Init()
if !isCclAware() {
bindGroup.bindNode = bindGroup.numaNode
} else {
bindGroup.bindNode = bindGroup.numaNode.SelectLighterLoadNode(topology.TopoTypeCluster)
}
bindGroup.bindNode.AddBind()
return &bindGroup
}
func attachTaskToTopoNode(task *bindTaskInfo, node *topology.TopoNode) {
task.newBindNode = node
task.nodeEle = node.AttachTask.PushBack(task)
node.AddBind()
}
func detachTaskFromTopoNode(task *bindTaskInfo, node *topology.TopoNode) {
task.newBindNode = nil
node.AttachTask.Remove(task.nodeEle)
node.SubBind()
}
func setTaskAffinity(tid uint64, node *topology.TopoNode) error {
var cpus []int
for cpu := node.Mask().Foreach(-1); cpu != -1; cpu = node.Mask().Foreach(cpu) {
cpus = append(cpus, cpu)
}
log.Info("bind ", tid, " to cpu ", cpus)
if err := sched.SetAffinity(tid, cpus); err != nil {
return err
}
return nil
}
func bindTaskToTopoNode(taskInfo *bindTaskInfo, node *topology.TopoNode) {
if err := setTaskAffinity(taskInfo.tid, node); err != nil {
log.Error(err)
taskInfo.retryEle = retryTasks.PushBack(taskInfo)
}
attachTaskToTopoNode(taskInfo, node)
}
func unbindTaskFromTopoNode(taskInfo *bindTaskInfo, node *topology.TopoNode) {
detachTaskFromTopoNode(taskInfo, node)
}
func migrateTaskToTopoNode(taskInfo *bindTaskInfo, newNode *topology.TopoNode) {
taskLoad := sysLoad.GetTaskLoad(taskInfo.tid)
oldNode := taskInfo.newBindNode
taskInfo.oldBindNode = oldNode
unbindTaskFromTopoNode(taskInfo, oldNode)
oldNode.SubLoad(taskLoad)
bindTaskToTopoNode(taskInfo, newNode)
newNode.AddLoad(taskLoad)
}
func attachTaskToGroup(task *bindTaskInfo, group *bindGroupInfo) {
task.groupEle = group.tasks.PushBack(task)
task.group = group
}
func detachTaskFromGroup(task *bindTaskInfo, group *bindGroupInfo) {
group.tasks.Remove(task.groupEle)
task.group = nil
task.groupEle = nil
if group.tasks.Len() == 0 {
delete(bindGroupMap, group.name)
}
}
func getTasksNumaAuto(tids []uint64) int {
var numaFaults [topology.NumaNodeNR]float64
var maxFaults float64
var numaID = -1
for _, tid := range tids {
topo := topology.GetThreadMemTopo(tid)
for index, count := range topo.NumaFaults {
numaFaults[index] += float64(count)
}
}
for i, faults := range numaFaults {
if faults > maxFaults {
maxFaults = faults
numaID = i
}
}
return numaID
}
func bindTasksToNuma(tid uint64, name string, numaID int) {
var taskInfo bindTaskInfo
var groupInfo *bindGroupInfo
var node *topology.TopoNode
var ok bool
if groupInfo, ok = bindGroupMap[name]; !ok {
groupInfo = makeBindGroupByNumaID(numaID)
bindGroupMap[name] = groupInfo
groupInfo.name = name
}
if isThreadBind(tid) {
return
}
if !isCoarseGrain() {
node = groupInfo.bindNode.SelectLighterBindNode(topology.TopoTypeCPU)
} else {
node = groupInfo.bindNode
}
taskInfo.tid = tid
attachTaskToGroup(&taskInfo, groupInfo)
bindTaskToTopoNode(&taskInfo, node)
bindTaskMap[tid] = &taskInfo
sysLoad.AddTask(tid)
}
// BindGroupAuto bind tasks in group
func BindGroupAuto(tids []uint64, name string) {
var numaID = -1
if isNumaAware() {
numaID = getTasksNumaAuto(tids)
}
if isNetAware() {
netNuma, err := netaffinity.GetProcessNetNuma(tids[0])
if err != nil {
log.Info(err)
} else if netNuma != -1 {
numaID = netNuma
log.Debugf("Set net affinity NUMA: %d", numaID)
}
}
if numaID == -1 {
node := topology.SelectTypeNode(topology.TopoTypeNUMA)
numaID = node.ID()
}
log.Debug("Bind group ", name, tids)
for _, tid := range tids {
bindTasksToNuma(tid, name, numaID)
}
}
func retryBindTasks() {
for ele := retryTasks.Front(); ele != nil; ele = ele.Next() {
taskInfo, ok := ele.Value.(*bindTaskInfo)
if !ok {
log.Error("get task infomation from failed tasks list failed")
continue
}
if err := setTaskAffinity(taskInfo.tid, taskInfo.newBindNode); err != nil {
log.Errorf("bind failed task %d failed, ignore task", taskInfo.tid)
UnbindTaskPolicy(taskInfo.tid)
}
}
}
func unbindAllTasks() {
for ele := retryTasks.Front(); ele != nil; ele = ele.Next() {
taskInfo, ok := ele.Value.(*bindTaskInfo)
if !ok {
log.Error("remove task infomation from failed tasks list failed")
continue
}
retryTasks.Remove(taskInfo.retryEle)
}
for tid := range bindTaskMap {
UnbindTaskPolicy(tid)
}
}
func autoGetGroups(tidsSlice *[]threadaffinity.TidsGroup, groupNum *int) {
*tidsSlice, *groupNum = threadaffinity.GetTidSlice()
}
func bindTasksAwareGroup(name string, tracetime int, block *ControlBlock) {
var tidsSlice []threadaffinity.TidsGroup
var groupNum int
log.Debugf("parse process %s affini\n", name)
if ShouldStartPtraceScan(block) {
threadaffinity.StartGroups(name, tracetime)
autoGetGroups(&tidsSlice, &groupNum)
if threadaffinity.GroupChanged() {
unbindAllTasks()
log.Info("group changed, rebind\n")
for i := 0; i < groupNum; i++ {
BindGroupAuto(tidsSlice[i].Tids, tidsSlice[i].GroupName)
}
}
}
}
func taskExist(taskName string) {
if pid, err := utils.GetPid(taskName); err == nil {
log.Debugf("get %s pid %d\n", taskName, pid)
delayTaskCh <- pid
threadaffinity.PidChanged(pid)
}
}
func bindTasksDelayAwareGroup(taskName string, traceTime int, block *ControlBlock) {
count := len(delayTaskCh)
for i := 0; i < count; i++ {
<-delayTaskCh
bindTasksAwareGroup(taskName, traceTime, block)
}
go taskExist(taskName)
scanHandler.ScanBoundTask()
}
func initScanHandler() {
scanHandler.BindGroup = BindGroupAuto
scanHandler.UnbindTask = UnbindTaskPolicy
scanHandler.ForEachBoundTask = ForEachBoundTask
}
func delayBindTasks(pid uint64) {
delayTaskCh <- pid
}
func bindTasksDelay() {
count := len(delayTaskCh)
for i := 0; i < count; i++ {
pid := <-delayTaskCh
scanHandler.ParseSchedGroupInfo(pid)
}
go procscan.ScanEachProc(delayBindTasks)
scanHandler.ScanBoundTask()
}
func bindTasksDirect() {
scanHandler.Run()
}
// BindTasksPolicy bind tasks config by user in environment variable
func BindTasksPolicy(block *ControlBlock) {
retryBindTasks()
if isAffinityAware() {
bindTasksDelayAwareGroup(taskName, traceTime, block)
} else {
if isNumaAware() {
bindTasksDelay()
} else {
bindTasksDirect()
}
}
}
// UnbindTaskPolicy release resouce allocted when bind tasks
func UnbindTaskPolicy(tid uint64) {
taskInfo, ok := bindTaskMap[tid]
if !ok {
return
}
log.Info("unbind task ", tid)
detachTaskFromGroup(taskInfo, taskInfo.group)
unbindTaskFromTopoNode(taskInfo, taskInfo.newBindNode)
if taskInfo.retryEle != nil {
retryTasks.Remove(taskInfo.retryEle)
taskInfo.retryEle = nil
}
delete(bindTaskMap, tid)
sysLoad.RemoveTask(tid)
}
// ForEachBoundTask foreach all bound tasks with func callback
func ForEachBoundTask(handler func(uint64)) {
for key := range bindTaskMap {
handler(key)
}
}
func cpuGap() int {
return (cpuGapPct << sysload.ScaleShift) / toPercentage
}
func nodeBalanceGap(node *topology.TopoNode) int {
return node.Mask().Weight() * cpuGap()
}
func shouldBalanceBetweenNode(srcNode *topology.TopoNode, dstNode *topology.TopoNode) bool {
diff := srcNode.GetLoad() - dstNode.GetLoad()
gap := nodeBalanceGap(srcNode)
return diff > gap
}
func groupTargetNode(srcNode *topology.TopoNode) *topology.TopoNode {
if srcNode.Type().Compare(topology.TopoTypeNUMA) >= 0 {
return nil
}
numaNode := srcNode.Parent(topology.TopoTypeNUMA)
return numaNode.SelectLighterLoadNode(srcNode.Type())
}
func groupWeight(group *bindGroupInfo) float64 {
var weight float64
for ele := group.tasks.Front(); ele != nil; ele = ele.Next() {
task, ok := (ele.Value).(*bindTaskInfo)
if !ok {
continue
}
weight += float64(sysLoad.GetTaskLoad(task.tid))
}
return weight
}
func shouldMigrateGroup(group *bindGroupInfo, srcNode *topology.TopoNode, dstNode *topology.TopoNode) bool {
if !shouldBalanceBetweenNode(srcNode, dstNode) {
return false
}
gWeight := groupWeight(group)
diff := srcNode.GetLoad() - dstNode.GetLoad()
return float64(diff) > gWeight
}
func migrateGroup(group *bindGroupInfo, srcNode *topology.TopoNode, dstNode *topology.TopoNode) {
for ele := group.tasks.Front(); ele != nil; ele = ele.Next() {
task, ok := (ele.Value).(*bindTaskInfo)
if !ok {
continue
}
newNode := dstNode.SelectLighterBindNode(task.newBindNode.Type())
migrateTaskToTopoNode(task, newNode)
}
srcNode.SubBind()
group.bindNode = dstNode
dstNode.AddBind()
}
func balanceGroup(group *bindGroupInfo) {
srcNode := group.bindNode
dstNode := groupTargetNode(srcNode)
if dstNode == nil {
return
}
if shouldMigrateGroup(group, srcNode, dstNode) {
migrateGroup(group, srcNode, dstNode)
return
}
}
type updateLoadCallback struct{}
// callback to update cpu load
func (callback *updateLoadCallback) Callback(node *topology.TopoNode) {
cpu := node.ID()
load := sysLoad.GetCPULoad(cpu)
node.SetLoad(load)
}
func updateNodeLoad() {
var callback updateLoadCallback
sysLoad.Update()
topology.ForeachTypeCall(topology.TopoTypeCPU, &callback)
}
// BalanceTaskPolicy balance CCLs inside NUMA according cpus' and tasks' load
func BalanceTaskPolicy() {
updateNodeLoad()
if !isCclAware() {
return
}
for _, groupInfo := range bindGroupMap {
balanceGroup(groupInfo)
}
}
// SwitchNumaAware select NUMA by NUMA faults if on, else by cpu load
func SwitchNumaAware(on bool) {
numaAwareOn = on
}
func isNumaAware() bool {
return numaAwareOn
}
// SwitchNetAware select NUMA node according to net affinity
func SwitchNetAware(on bool) {
netAwareOn = on
}
func isNetAware() bool {
return netAwareOn
}
// SwitchCclAware choose CCL futher inside NUMA futher if on
func SwitchCclAware(on bool) {
cclAwareOn = on
}
func isCclAware() bool {
return cclAwareOn
}
// SwitchCoarseGrain choose cpu futher inside CCL futher if off
func SwitchCoarseGrain(on bool) {
coarseGrain = on
}
func isCoarseGrain() bool {
return coarseGrain
}
// SwitchAffinityAware enable detecting thread affinity automaticly
func SwitchAffinityAware(on bool) {
affinityAware = on
}
func isAffinityAware() bool {
return affinityAware
}
// SetAffinityTaskName set the target process according to the comm given
func SetAffinityTaskName(name string) {
taskName = name
}
// SetAffinityTraceTime set the length of tracing time
func SetAffinityTraceTime(time int) {
traceTime = time
}
// Init init modules we depends on
func Init() error {
if err := topology.InitTopo(); err != nil {
log.Error("init topology failed")
return err
}
retryTasks.Init()
sysLoad.Init()
initScanHandler()
return nil
}
// PtraceScanStart start the ptrace scan
func PtraceScanStart(block *ControlBlock) {
log.Info("threads scan on")
atomic.StoreInt32(&(block.ptraceScanSwitch), 1)
}
// PtraceScanEnd stop the ptrace scan
func PtraceScanEnd(block *ControlBlock) {
log.Info("threads scan off")
atomic.StoreInt32(&(block.ptraceScanSwitch), 0)
}
// ShouldStartPtraceScan indicate whether scanning should be done
func ShouldStartPtraceScan(block *ControlBlock) bool {
res := atomic.LoadInt32(&(block.ptraceScanSwitch))
return res == 1
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
package policy
import (
"fmt"
"math/rand"
"os"
"testing"
"time"
"gitee.com/wisdom-advisor/common/sched"
"gitee.com/wisdom-advisor/common/testlib"
"gitee.com/wisdom-advisor/common/utils"
)
const nodeNum = 4
const threadNum = 2
const threadLimit = 6000
const faultsLimit = 500
const testCount = 1000
const uintMin = 0
const allSame = -1
const chipNum = 1
const percent100 = 100
const testSleepTime = 1
const nodeCPUNum = 4
const testThreadNum = 1
const groupThreadNum = 4
const coarseGrainThreadNum = 1
const noMigrateGap = 20
const migrateGap = 40
const numerousTaskNum = 500
const acceptTime = 1
func getMaxIndex(arr []uint64) int {
var max uint64 = uintMin
ret := allSame
for i, elem := range arr {
if elem > max {
max = elem
ret = i
}
}
return ret
}
func addCclLoad(cclID int, cpuNumPerCluster int, cpuLoadStub *testlib.CPULoadStub, load int) {
for i := 0; i < cpuNumPerCluster; i++ {
cpuLoadStub.AddLoad(cclID*cpuNumPerCluster+i, load, testSleepTime)
}
cpuLoadStub.DeployLoad()
}
// TestGetNumaDependOnFaults is random test for getNumaDependOnFaults
func TestGetNumaDependOnFaults(t *testing.T) {
var numaID int
utils.ProcDir = "./tmp/proc/"
tidsMap := make(map[uint64]uint64, threadNum)
var tids []uint64
var faultsNum [nodeNum]uint64
var tmp uint64
var data []byte
var path string
var expect int
rand.Seed(time.Now().Unix())
for len(tidsMap) < threadNum {
tmp = uint64(rand.Int63n(threadLimit))
tidsMap[tmp] = tmp
}
for _, tid := range tidsMap {
tids = append(tids, tid)
data = data[:0]
for i := 0; i < nodeNum; i++ {
tmp = uint64(rand.Int63n(faultsLimit))
num := fmt.Sprintf("faults on node %d: %d\n", i, tmp)
faultsNum[i] = faultsNum[i] + tmp
data = append(data, []byte(num)...)
}
path = fmt.Sprintf("./tmp/proc/%d/", tid)
testlib.BuildFakePathWithData(path, "task_fault_siblings", data)
}
expect = getMaxIndex(faultsNum[:])
if expect == allSame {
return
}
numaID = getTasksNumaAuto(tids)
err := os.RemoveAll("./tmp/")
if err != nil {
fmt.Println("Remove path error")
}
if numaID != expect {
t.Errorf(" Select %d; expected %d", numaID, expect)
}
}
// TestAll is multiple test for getNumaDependOnFaults
func TestAll(t *testing.T) {
for i := 0; i < testCount; i++ {
t.Run("TestGetNumaDependOnFaults", TestGetNumaDependOnFaults)
}
}
func checkBind(tids []uint64, targetCcl int, cpuNumPerCluster int, t *testing.T) {
tidNum := len(tids)
cpuRec := make(map[uint64]bool, tidNum)
for _, tid := range tids {
cpus, err := testlib.GetAffinityStub(tid)
if err != nil {
t.Errorf("get affinity failed\n")
}
ccl := cpus[0] / cpuNumPerCluster
if ccl != targetCcl {
t.Errorf("bind ccl wrong, expect %d, actually %d\n",
targetCcl, ccl)
}
if _, ok := cpuRec[tid]; ok {
t.Errorf("cpu already bind")
} else {
cpuRec[tid] = true
}
}
}
// func TestBindTaskPolicy test BindTaskPolicy
func TestBindTaskPolicy(t *testing.T) {
var block ControlBlock
topoStub, cpuLoadStub, taskStub := testlib.InitStub()
tids := taskStub.CreateTasks(groupThreadNum)
cpuNumPerCluster := topoStub.CPUNum / topoStub.ClusterNum
SwitchCclAware(true)
if err := Init(); err != nil {
t.Errorf("init policy failed\n")
}
targetCcl := rand.Intn(topoStub.ClusterNum)
for i := 0; i < topoStub.ClusterNum; i++ {
if i != targetCcl {
addCclLoad(i, cpuNumPerCluster, cpuLoadStub, percent100)
}
}
time.Sleep(time.Duration(testSleepTime) * time.Second)
BalanceTaskPolicy()
BindTasksPolicy(&block)
checkBind(tids, targetCcl, cpuNumPerCluster, t)
for i := 0; i < len(tids); i++ {
UnbindTaskPolicy(tids[i])
}
SwitchCclAware(false)
testlib.CleanStub()
}
// TestDelayBindTasks test delayBindTasks
func TestDelayBindTasks(t *testing.T) {
var block ControlBlock
_, _, taskStub := testlib.InitStub()
SwitchNumaAware(true)
tids := taskStub.CreateTasks(testThreadNum)
if err := Init(); err != nil {
t.Errorf("init policy failed\n")
}
// identify task for delay bind
BindTasksPolicy(&block)
if _, err := testlib.GetAffinityStub(tids[0]); err == nil {
t.Errorf("bind task early\n")
}
time.Sleep(time.Duration(testSleepTime) * time.Second)
// delay bind task
BindTasksPolicy(&block)
if _, err := testlib.GetAffinityStub(tids[0]); err != nil {
t.Errorf("get affinity failed %s\n", err.Error())
}
UnbindTaskPolicy(tids[0])
SwitchNumaAware(false)
testlib.CleanStub()
}
func setAffinityFailed(tid uint64, cpu []int) error {
return fmt.Errorf("setAffinityFailed stub")
}
func normalRetryTest(tid uint64, t *testing.T) {
var block ControlBlock
// set affinity failed when bind task
sched.SetAffinity = setAffinityFailed
BindTasksPolicy(&block)
// retry failed task, set affinity sucess
sched.SetAffinity = testlib.SetAffinityStub
BindTasksPolicy(&block)
if _, err := testlib.GetAffinityStub(tid); err != nil {
t.Error(err.Error())
}
// clean up tid bind info
UnbindTaskPolicy(tid)
}
func taskExitBeforeRetryTest(tid uint64, taskStub *testlib.TaskStub, t *testing.T) {
var block ControlBlock
// set affinity failed
sched.SetAffinity = setAffinityFailed
BindTasksPolicy(&block)
// failed task exit
taskStub.DeleteTid(tid)
// retry failed task, pass if no coredump
BindTasksPolicy(&block)
// release resource as retry failed
if isThreadBind(tid) {
t.Errorf("thread info still exist after retry failed\n")
}
}
// TestRetryBindTasks is a test for retryBindTasks function
func TestRetryBindTasks(t *testing.T) {
_, _, taskStub := testlib.InitStub()
tids := taskStub.CreateTasks(testThreadNum)
if err := Init(); err != nil {
t.Errorf("policy Init failed\n")
}
normalRetryTest(tids[0], t)
taskExitBeforeRetryTest(tids[0], taskStub, t)
testlib.CleanStub()
}
// TestUnbindTaskPolicy test UnbindTaskPolicy
func TestUnbindTaskPolicy(t *testing.T) {
var block ControlBlock
_, _, taskStub := testlib.InitStub()
tids := taskStub.CreateTasks(testThreadNum)
if err := Init(); err != nil {
t.Errorf("init policy failed\n")
}
BindTasksPolicy(&block)
if !isThreadBind(tids[0]) {
t.Errorf("task bind failed\n")
}
taskStub.DeleteTid(tids[0])
BindTasksPolicy(&block)
if isThreadBind(tids[0]) {
t.Errorf("task unbind failed\n")
}
testlib.CleanStub()
}
// TestMigration test Migration mechanism
func TestMigration(t *testing.T) {
var block ControlBlock
topoStub, cpuLoadStub, taskStub := testlib.InitStub()
SwitchCclAware(true)
if err := Init(); err != nil {
t.Error("init policy failed")
}
tids := taskStub.CreateTasks(testThreadNum)
cpuNumPerCluster := topoStub.CPUNum / topoStub.ClusterNum
srcCcl := rand.Intn(topoStub.ClusterNum)
for i := 0; i < topoStub.ClusterNum; i++ {
if i != srcCcl {
addCclLoad(i, cpuNumPerCluster, cpuLoadStub, percent100)
}
}
time.Sleep(time.Duration(testSleepTime) * time.Second)
BalanceTaskPolicy()
BindTasksPolicy(&block)
cclNumPerNuma := topoStub.ClusterNum / topoStub.NumaNum
// make sure dstCcl and srcCcl in same NUMA
dstCcl := srcCcl/cclNumPerNuma*cclNumPerNuma +
(srcCcl+1)%cclNumPerNuma
for i := 0; i < topoStub.ClusterNum; i++ {
if i != dstCcl {
addCclLoad(i, cpuNumPerCluster, cpuLoadStub, percent100)
}
}
time.Sleep(time.Duration(testSleepTime) * time.Second)
BalanceTaskPolicy()
cpus, err := testlib.GetAffinityStub(tids[0])
if err != nil {
t.Error(err.Error())
}
if cpus[0]/cpuNumPerCluster != dstCcl {
t.Errorf("migrate failed\n")
}
UnbindTaskPolicy(tids[0])
SwitchCclAware(false)
testlib.CleanStub()
}
func getBindCcl(tid uint64, cpuNumPerCluster int, t *testing.T) int {
cpus, err := testlib.GetAffinityStub(tid)
if err != nil {
t.Errorf("get affinity failed")
return -1
}
return cpus[0] / cpuNumPerCluster
}
// TestMigrationGap test migration gap
func TestMigrationWithGap(t *testing.T) {
var block ControlBlock
topoStub, cpuLoadStub, taskStub := testlib.InitStub()
SwitchCclAware(true)
if err := Init(); err != nil {
t.Error("init policy failed")
}
tids := taskStub.CreateTasks(testThreadNum)
cpuNumPerCluster := topoStub.CPUNum / topoStub.ClusterNum
// bind a init ccl
BindTasksPolicy(&block)
srcTarget := getBindCcl(tids[0], cpuNumPerCluster, t)
// test load within migration gap
addCclLoad(srcTarget, cpuNumPerCluster, cpuLoadStub, noMigrateGap)
time.Sleep(time.Duration(testSleepTime) * time.Second)
BalanceTaskPolicy()
dstTarget := getBindCcl(tids[0], cpuNumPerCluster, t)
if dstTarget != srcTarget {
t.Errorf("migration occurs with gap %d\n", noMigrateGap)
}
// test load beyond migration gap
addCclLoad(srcTarget, cpuNumPerCluster, cpuLoadStub, migrateGap)
time.Sleep(time.Duration(testSleepTime) * time.Second)
BalanceTaskPolicy()
dstTarget = getBindCcl(tids[0], cpuNumPerCluster, t)
if dstTarget == srcTarget {
t.Errorf("not migrate with gap %d\n", migrateGap)
}
UnbindTaskPolicy(tids[0])
SwitchCclAware(false)
testlib.CleanStub()
}
// TestMigrationWithTaskLoad test migration with taskload
func TestMigrationWithTaskLoad(t *testing.T) {
var block ControlBlock
topoStub, _, taskStub := testlib.InitStub()
SwitchCclAware(true)
if err := Init(); err != nil {
t.Error("init policy failed")
}
tids := taskStub.CreateTasks(groupThreadNum)
cpuNumPerCluster := topoStub.CPUNum / topoStub.ClusterNum
// bind a init ccl
BindTasksPolicy(&block)
srcTarget := getBindCcl(tids[0], cpuNumPerCluster, t)
// add task load
for _, tid := range tids {
taskStub.AddLoad(tid, migrateGap, testSleepTime)
}
time.Sleep(time.Duration(testSleepTime) * time.Second)
BalanceTaskPolicy()
for _, tid := range tids {
dstTarget := getBindCcl(tid, cpuNumPerCluster, t)
if dstTarget != srcTarget {
t.Errorf("task laod cause migrate")
}
}
for _, tid := range tids {
UnbindTaskPolicy(tid)
}
SwitchCclAware(false)
testlib.CleanStub()
}
// TestSwitchCclAware test cclAware switch
func TestSwitchCclAware(t *testing.T) {
var block ControlBlock
topoStub, _, taskStub := testlib.InitStub()
tids := taskStub.CreateTasks(testThreadNum)
SwitchCoarseGrain(true)
if err := Init(); err != nil {
t.Errorf("init policy failed\n")
}
// turn ccl aware on
SwitchCclAware(true)
cpuNumPerCluster := topoStub.CPUNum / topoStub.ClusterNum
BindTasksPolicy(&block)
cpus, err := testlib.GetAffinityStub(tids[0])
if err != nil {
t.Errorf("get affinity failed")
}
if cpuNumPerCluster != len(cpus) {
t.Errorf("ccl aware on: affinity cpu number %d is wrong", len(cpus))
}
for i := 0; i < len(cpus)-1; i++ {
if cpus[i]/cpuNumPerCluster != cpus[i+1]/cpuNumPerCluster {
t.Errorf("bind cpus are not in one cluster\n")
}
}
UnbindTaskPolicy(tids[0])
// turn ccl aware off
SwitchCclAware(false)
cpuNumPerNuma := topoStub.CPUNum / topoStub.NumaNum
BindTasksPolicy(&block)
cpus, err = testlib.GetAffinityStub(tids[0])
if err != nil {
t.Error(err.Error())
}
if cpuNumPerNuma != len(cpus) {
t.Errorf("ccl aware off: affinity cpu number %d is wrong", len(cpus))
}
for i := 0; i < len(cpus)-1; i++ {
if int(cpus[i])/cpuNumPerNuma != int(cpus[i+1])/cpuNumPerNuma {
t.Errorf("bind cpus are not in one NUMA\n")
}
}
UnbindTaskPolicy(tids[0])
testlib.CleanStub()
}
// TestSwitchCoarseGrain test coarseGrain switch
func TestSwitchCoarseGrain(t *testing.T) {
var block ControlBlock
_, _, taskStub := testlib.InitStub()
tids := taskStub.CreateTasks(testThreadNum)
if err := Init(); err != nil {
t.Errorf("init policy failed\n")
}
// turn coarseGrain on
SwitchCoarseGrain(true)
BindTasksPolicy(&block)
cpus, err := testlib.GetAffinityStub(tids[0])
if err != nil {
t.Error(err.Error())
}
fmt.Printf("coarseOn cpus %v", cpus)
if len(cpus) == coarseGrainThreadNum {
t.Errorf("affinity cpu number is 1 when coarseGrain on")
}
UnbindTaskPolicy(tids[0])
// turn coarseGrain off
SwitchCoarseGrain(false)
BindTasksPolicy(&block)
cpus, err = testlib.GetAffinityStub(tids[0])
if err != nil {
t.Errorf("get affinity failed")
}
if len(cpus) != 1 {
t.Errorf("affinity cpu number is not 1 when coarseGrain off")
}
UnbindTaskPolicy(tids[0])
testlib.CleanStub()
}
// TestBindNumerousTasks test time to bind numerous tasks
func TestBindNumerousTasks(t *testing.T) {
var block ControlBlock
_, _, taskStub := testlib.InitStub()
tasks := make([][]uint64, numerousTaskNum, numerousTaskNum)
for i := 0; i < numerousTaskNum; i++ {
tasks[i] = taskStub.CreateTasks(groupThreadNum)
}
if err := Init(); err != nil {
t.Errorf("init policy failed\n")
}
BindTasksPolicy(&block)
time.Sleep(time.Duration(testSleepTime) * time.Second)
for i := 0; i < numerousTaskNum; i++ {
for _, tid := range tasks[i] {
if _, err := testlib.GetAffinityStub(tid); err != nil {
t.Errorf("bind task failed\n")
}
UnbindTaskPolicy(tid)
}
}
testlib.CleanStub()
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
// Package procscan implements utility to scan procfs to get tasks group
// from process environment variable
package procscan
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"regexp"
"strconv"
"strings"
"gitee.com/wisdom-advisor/common/utils"
log "github.com/sirupsen/logrus"
)
const limitEnvSize = 1048576 // 1M bytes
// SchedGroupHandler handler to deal with task groups configured by user in environment variables
type SchedGroupHandler struct {
BindGroup func(tids []uint64, groupName string)
UnbindTask func(tid uint64)
ForEachBoundTask func(func(tid uint64))
}
func (handler SchedGroupHandler) isMember(path string, member map[string]int) bool {
data, err := ioutil.ReadFile(path + "/comm")
if err != nil {
log.Error(path + " comm read fail")
return false
}
_, ok := member[strings.Replace(string(data), "\n", "", -1)]
return ok
}
func (handler SchedGroupHandler) bindMembers(name string, member []string, pid uint64) {
var tids []uint64
memberMap := make(map[string]int, len(member))
groupName := fmt.Sprintf("%v", pid) + "_" + name
for _, line := range member {
memberMap[line] = 0
}
files, err := ioutil.ReadDir(utils.ProcDir + fmt.Sprintf("%v", pid) + "/task/")
if err != nil {
return
}
for _, file := range files {
if tid, err := strconv.ParseUint(file.Name(), utils.DecimalBase, utils.Uint64Bits); err != nil {
continue
} else {
fullPath := utils.ProcDir + fmt.Sprintf("%v", pid) + "/task/" + file.Name()
if handler.isMember(fullPath, memberMap) {
tids = append(tids, tid)
}
}
}
handler.BindGroup(tids, groupName)
}
// ParseSchedGroupInfo handle tasks groups of task with pid
func (handler SchedGroupHandler) ParseSchedGroupInfo(pid uint64) {
reg := regexp.MustCompile(`__SCHED_GROUP__(\S+)=(\S+)`)
f, err := os.Open(utils.ProcDir + fmt.Sprintf("%v", pid) + "/environ")
if err != nil {
log.Debugf(fmt.Sprintf("%v", pid) + " environ open fail")
return
}
defer f.Close()
buf := bytes.NewBuffer(make([]byte, 0, limitEnvSize))
_, err = buf.ReadFrom(f)
if err != nil {
log.Debugf(fmt.Sprintf("%v", pid) + " environ read fail")
return
}
lines := strings.Split(buf.String(), "\000")
for _, line := range lines {
params := reg.FindStringSubmatch(line)
if params != nil {
handler.bindMembers(params[1], strings.FieldsFunc(params[2],
func(c rune) bool { return c == ',' }), pid)
}
}
}
// ScanEachProc foreach pid under procfs with func callback
func ScanEachProc(handler func(pid uint64)) {
files, err := ioutil.ReadDir(utils.ProcDir)
if err != nil {
return
}
for _, file := range files {
if pid, err := strconv.ParseUint(file.Name(), utils.DecimalBase, utils.Uint64Bits); err != nil {
continue
} else {
handler(pid)
}
}
}
func isTaskExisted(tid uint64) bool {
return utils.IsFileExisted(utils.ProcDir + fmt.Sprintf("%v", tid))
}
func (handler SchedGroupHandler) checkBoundTask(tid uint64) {
if !isTaskExisted(tid) {
handler.UnbindTask(tid)
}
}
// ScanBoundTask handle exits tasks
func (handler SchedGroupHandler) ScanBoundTask() {
handler.ForEachBoundTask(handler.checkBoundTask)
}
// Run scan all tasks under procfs to find and handle all tasks groups
func (handler SchedGroupHandler) Run() {
ScanEachProc(handler.ParseSchedGroupInfo)
handler.ScanBoundTask()
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
package procscan
import (
"fmt"
"os"
"testing"
"gitee.com/wisdom-advisor/common/testlib"
"gitee.com/wisdom-advisor/common/utils"
)
const constZero = 0
const memberSize = 5
// TestIsMember test the member check function
func TestIsMember(t *testing.T) {
utils.ProcDir = "./tmp/proc/"
var handler SchedGroupHandler
var data []byte
member := make(map[string]int, memberSize)
path := fmt.Sprintf("./tmp/proc/23/")
data = append(data, []byte("a1")...)
var ret bool
testlib.BuildFakePathWithData(path, "comm", data)
ret = handler.isMember("./tmp/proc/23/", member)
if ret != false {
t.Errorf("expect false, get ture")
}
err := os.RemoveAll("./tmp/")
if err != nil {
fmt.Println("Remove path error")
}
}
// TestIsMember2 test the member check function
func TestIsMember2(t *testing.T) {
utils.ProcDir = "./tmp/proc/"
var handler SchedGroupHandler
var data []byte
member := make(map[string]int, memberSize)
path := fmt.Sprintf("./tmp/proc/23/")
data = append(data, []byte("a1")...)
var ret bool
member["a1"] = constZero
testlib.BuildFakePathWithData(path, "comm", data)
ret = handler.isMember("./tmp/proc/23/", member)
if ret != true {
t.Errorf("expect false, get ture")
}
err := os.RemoveAll("./tmp/")
if err != nil {
fmt.Println("Remove path error")
}
}
// TestIsMember3 test the member check function with line break
func TestIsMember3(t *testing.T) {
utils.ProcDir = "./tmp/proc/"
var handler SchedGroupHandler
var data []byte
member := make(map[string]int, memberSize)
path := fmt.Sprintf("./tmp/proc/23/")
data = append(data, []byte("a1\n")...)
var ret bool
member["a1"] = constZero
testlib.BuildFakePathWithData(path, "comm", data)
ret = handler.isMember("./tmp/proc/23/", member)
if ret != true {
t.Errorf("expect false, get ture")
}
err := os.RemoveAll("./tmp/")
if err != nil {
fmt.Println("Remove path error")
}
}
// TestIsTaskExisted check file existing check
func TestIsTaskExisted(t *testing.T) {
utils.ProcDir = "./tmp/proc/"
path := fmt.Sprintf("./tmp/proc/0/")
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
fmt.Println("Mkdir error")
return
}
ret := isTaskExisted(constZero)
if ret != true {
t.Errorf("expect false, get ture")
}
err = os.RemoveAll("./tmp/")
if err != nil {
fmt.Println("Remove path error")
}
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
// Package ptrace provides functions for ptrace scanning
package ptrace
import (
"fmt"
"syscall"
"unsafe"
)
// UserPtRegsArm64 are the registers contain process context
type UserPtRegsArm64 struct {
Regs [31]uint64
SP uint64
PC uint64
PState uint64
}
// Ptrace wraps ptrace syscall
func Ptrace(request uint64, pid uint64, addr uint64, data uint64) error {
if _, _, errno := syscall.RawSyscall6(syscall.SYS_PTRACE,
uintptr(request), uintptr(pid), uintptr(addr), uintptr(data), 0, 0); errno != 0 {
return fmt.Errorf("Ptrace fail no:%d", errno)
}
return nil
}
// Seize is to seize one thread which should be done before collecting
func Seize(pid uint64) error {
return Ptrace(syscall.PTRACE_SEIZE, pid, 0, 0)
}
// Detach is to end the seizing of one thread
func Detach(pid uint64) error {
return Ptrace(syscall.PTRACE_DETACH, pid, 0, 0)
}
// Interrupt is to interrupt one thread under seizing
func Interrupt(pid uint64) error {
return Ptrace(syscall.PTRACE_INTERRUPT, pid, 0, 0)
}
// Continue is to continue one thread being interrupted
func Continue(pid uint64) error {
return Ptrace(syscall.PTRACE_CONT, pid, 0, uint64(syscall.SIGTRAP))
}
// CatchSyscall is to interrupt the thread at next syscall
func CatchSyscall(pid uint64) error {
return Ptrace(syscall.PTRACE_SYSCALL, pid, 0, 0)
}
// CollectSyscall is to collect the regs when one thread is interrupted
func CollectSyscall(pid uint64) UserPtRegsArm64 {
var regs UserPtRegsArm64
var iovec syscall.Iovec
iovec.Base = (*byte)(unsafe.Pointer(&regs))
iovec.Len = uint64(unsafe.Sizeof(regs))
Ptrace(syscall.PTRACE_GETREGSET, pid, 1, uint64(uintptr(unsafe.Pointer(&iovec))))
return regs
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
package ptrace
import (
"errors"
"fmt"
"gitee.com/wisdom-advisor/common/netaffinity"
"gitee.com/wisdom-advisor/common/utils"
log "github.com/sirupsen/logrus"
unix "golang.org/x/sys/unix"
"io/ioutil"
"net"
"os"
"regexp"
"runtime"
"strconv"
"sync"
"syscall"
"time"
)
type syscallCount struct {
NetAccess uint64
FutexMap map[uint64]int
IOGetEvents uint64
}
// ProcessFeature is to decribe the feature of one process
type ProcessFeature struct {
Pid uint64
SysCount syscallCount
}
// ProcDir is path of proc sysfs
var ProcDir = "/proc/"
// FutextDetect is to Detect Futex
func FutextDetect(regs UserPtRegsArm64, process *ProcessFeature) error {
if regs.Regs[8] != unix.SYS_FUTEX {
return nil
}
if _, ok := process.SysCount.FutexMap[regs.Regs[0]]; !ok {
process.SysCount.FutexMap[regs.Regs[0]] = 1
} else {
process.SysCount.FutexMap[regs.Regs[0]] = process.SysCount.FutexMap[regs.Regs[0]] + 1
}
return nil
}
// ParseSyscall is to Parse syscall info
func ParseSyscall(regs UserPtRegsArm64, process *ProcessFeature) error {
sysMap := map[uint64]func(regs UserPtRegsArm64, process *ProcessFeature) error{
unix.SYS_READ: CollectSockAccess,
unix.SYS_WRITE: CollectSockAccess,
unix.SYS_SENDTO: CollectSockAccess,
unix.SYS_RECVFROM: CollectSockAccess,
unix.SYS_IO_GETEVENTS: func(regs UserPtRegsArm64, process *ProcessFeature) error {
process.SysCount.IOGetEvents = process.SysCount.IOGetEvents + 1
return nil
},
}
if handler, ok := sysMap[regs.Regs[8]]; ok {
return handler(regs, process)
}
return nil
}
func isSocketfd(link string) (bool, uint64) {
reg := regexp.MustCompile(`socket:\[(\d+)\]`)
params := reg.FindStringSubmatch(link)
if params != nil {
inode, err := strconv.ParseUint(params[1], utils.DecimalBase, utils.Uint64Bits)
if err == nil {
return true, inode
}
}
return false, 0
}
func isNetSocket(inode uint64) (bool, net.IP) {
netFiles := []string{ProcDir + "/net/tcp", ProcDir + "/net/udp", ProcDir + "/net/tcp6", ProcDir + "/net/udp6"}
for _, file := range netFiles {
inodes, err := netaffinity.GetInodeIP(file)
if err != nil {
log.Error(err)
continue
}
if ip, ok := inodes[inode]; ok {
return true, ip
}
}
return false, nil
}
// CollectSockAccess is to collect net accesses through fd
func CollectSockAccess(regs UserPtRegsArm64, process *ProcessFeature) error {
return collectSockAccess(process, regs.Regs[0])
}
func collectSockAccess(process *ProcessFeature, fd uint64) error {
link, err := os.Readlink(fmt.Sprintf("%s/%d/fd/%d", ProcDir, process.Pid, fd))
if err != nil {
return errors.New("collectRead Readlink fail")
}
if res, inode := isSocketfd(link); res {
if res, _ := isNetSocket(inode); res {
process.SysCount.NetAccess = process.SysCount.NetAccess + 1
}
}
return nil
}
// ParseLoop is the loop which collects syscallinfo
func ParseLoop(pid uint64, stopCh chan int, process *ProcessFeature, wg *sync.WaitGroup, tgid uint64,
ParseSyscallHandler func(regs UserPtRegsArm64, process *ProcessFeature) error) {
var status syscall.WaitStatus
var isEntry = true
runtime.LockOSThread()
defer wg.Done()
if err := Seize(pid); err != nil {
log.Debug("Seize fail\n")
return
}
if err := Interrupt(pid); err != nil {
log.Debug("Interrupt fail\n")
goto out
}
for {
syscall.Wait4(int(pid), &status, syscall.WALL, nil)
if status.Exited() {
return
} else if status.Stopped() {
break
}
}
for {
if err := CatchSyscall(pid); err != nil {
log.Debug("CatchSyscall fail\n")
goto out
}
for {
if wpid, _ := syscall.Wait4(int(pid), &status,
syscall.WNOHANG, nil); wpid > 0 {
if status.Stopped() {
break
} else if status.Exited() {
return
}
}
select {
case <-stopCh:
goto out
default:
}
}
if isEntry {
regs := CollectSyscall(pid)
if err := ParseSyscallHandler(regs, process); err != nil {
log.Info(err)
}
isEntry = false
} else {
isEntry = true
}
}
out:
if err := Detach(pid); err != nil {
Interrupt(pid)
syscall.Wait4(int(pid), &status, syscall.WALL, nil)
if err := Detach(pid); err != nil {
log.Debug("Detach fail\n")
}
}
}
// DoCollect is to collect the syscall info of one process during timeout time
func DoCollect(pid uint64, timeout int,
ParseSyscallHandler func(regs UserPtRegsArm64, process *ProcessFeature) error) ([]*ProcessFeature, error) {
var threadsInfo []*ProcessFeature
var wg sync.WaitGroup
stopCh := make(chan int)
runtime.LockOSThread()
files, err := ioutil.ReadDir(ProcDir + fmt.Sprintf("%v", pid) + "/task/")
if err != nil {
return threadsInfo, errors.New("get process info fail")
}
for _, file := range files {
if tid, err := strconv.ParseUint(file.Name(), utils.DecimalBase, utils.Uint64Bits); err != nil {
continue
} else {
if utils.IsFileExisted((ProcDir + fmt.Sprintf("%v", pid) + "/task/" + file.Name())) {
var thread ProcessFeature
thread.Pid = tid
thread.SysCount.FutexMap = make(map[uint64]int)
threadsInfo = append(threadsInfo, &thread)
wg.Add(1)
go ParseLoop(tid, stopCh, &thread, &wg, pid, ParseSyscallHandler)
}
}
}
time.Sleep(time.Duration(timeout) * time.Second)
close(stopCh)
wg.Wait()
return threadsInfo, nil
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
// Package sched implements utility to set schedule properties of tasks
package sched
import (
"fmt"
"math/bits"
"syscall"
"unsafe"
)
const (
cpuSetSize = 1024
cpuBits = bits.UintSize
cpuBytes = cpuBits / 8
)
type cpuSet [cpuSetSize / cpuBits]uintptr
// SetAffinity is handler to set thread cpu affinity.
var SetAffinity func(tid uint64, cpu []int) error
func init() {
SetAffinity = setAffinity
}
func setAffinity(tid uint64, cpu []int) error {
var mask cpuSet
for _, cpuID := range cpu {
mask[cpuID/cpuBits] |= 1 << (cpuID % cpuBits)
}
if _, _, errno := syscall.RawSyscall(syscall.SYS_SCHED_SETAFFINITY,
uintptr(tid), uintptr(len(mask)*cpuBytes), uintptr(unsafe.Pointer(&mask[0]))); errno != 0 {
return fmt.Errorf("tid:%d setaffinity fails:%s", tid, syscall.Errno(errno))
}
return nil
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
package sched
import (
"fmt"
"os/exec"
"strconv"
"strings"
"testing"
"gitee.com/wisdom-advisor/common/utils"
)
const targetCPU = 0
const initLen = 1
const cpuFiledIndex = 5
// TestsetAffinity test function setAffinity
func TestSetAffinity(t *testing.T) {
var cpu int64
bashCmd := exec.Command("/bin/bash")
if err := bashCmd.Start(); err != nil {
t.Errorf("start command bash failed\n")
}
pid := bashCmd.Process.Pid
cpus := make([]int, initLen, initLen)
cpus[0] = targetCPU
if err := setAffinity(uint64(pid), cpus); err != nil {
t.Errorf("setAffinity failed: %s\n", err.Error())
}
tasksetCmd := exec.Command("bash", "-c", fmt.Sprintf("/usr/bin/taskset -pc %d", pid))
out, err := tasksetCmd.Output()
if err != nil {
t.Errorf("run taskset failed: %s\n", err.Error())
}
fields := strings.Fields(string(out))
cpu, err = strconv.ParseInt(fields[cpuFiledIndex], utils.DecimalBase, utils.Uint64Bits)
if err != nil {
t.Errorf("convert %s to int failed\n", fields[cpuFiledIndex])
}
if cpu != targetCPU {
t.Errorf("expect bind cpu %d, actual %d\n", targetCPU, cpu)
}
exec.Command("bash", "-c", fmt.Sprintf("kill -9 %d", pid)).Run()
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
// Package sysload implements utility go get cpu and task load
// from stat files in procfs
package sysload
import (
"bufio"
"container/list"
"fmt"
"os"
"strconv"
"strings"
"time"
"gitee.com/wisdom-advisor/common/utils"
log "github.com/sirupsen/logrus"
)
// #include <unistd.h>
import "C"
var clockTicks uint64
// ScaleShift is in order to avoid float compute
const ScaleShift uint64 = 10
const nsecPerSec uint64 = 1000000000
const cpuNameIndex = 0
const cpuIDIndex = 3
type timeUsage struct {
lastUpdate time.Time
user uint64
system uint64
load int
}
func init() {
clockTicks = uint64(C.sysconf(C._SC_CLK_TCK))
}
func scaleUp(i uint64) uint64 {
return i << ScaleShift
}
func (usage *timeUsage) init(now *time.Time, user uint64, system uint64) {
usage.lastUpdate = *now
usage.user = user
usage.system = system
}
func (usage *timeUsage) update(now *time.Time, user uint64, system uint64) {
duration := uint64(now.Sub(usage.lastUpdate)) * clockTicks / nsecPerSec
if duration == 0 {
return
}
usage.load = int(scaleUp((user-usage.user)+(system-usage.system)) / (duration))
usage.lastUpdate = *now
usage.user = user
usage.system = system
}
// Load represent cpu load or task load with cpu usage
type Load struct {
id uint64
usage timeUsage
}
func (load *Load) set(id uint64) {
load.id = id
}
// Init of Load init load with timestamp
func (load *Load) Init(now *time.Time, user uint64, system uint64) {
load.usage.init(now, user, system)
}
// Update of Load update load with new timestamp
func (load *Load) Update(now *time.Time, user uint64, system uint64) {
load.usage.update(now, user, system)
}
// SystemLoad provides cpus and tasks load according running time
type SystemLoad struct {
cpusLoad []Load
tasksLoad list.List
}
type cpuStat struct {
cpuNum uint64
user uint64
system uint64
}
func parseCPUStatLine(str string) (cpuStat, error) {
const cpuUserTimeIndex = 1
const cpuSystemTimeIndex = 3
var stat cpuStat
var err error
statFields := strings.Fields(str)
cpu := statFields[cpuNameIndex]
stat.user, err = strconv.ParseUint(statFields[cpuUserTimeIndex], utils.DecimalBase, utils.Uint64Bits)
if err != nil {
err = fmt.Errorf("convert cpu user time from %s failed", statFields[cpuUserTimeIndex])
return stat, err
}
stat.system, err = strconv.ParseUint(statFields[cpuSystemTimeIndex], utils.DecimalBase, utils.Uint64Bits)
if err != nil {
err = fmt.Errorf("convert cpu system time from %s failed", statFields[cpuSystemTimeIndex])
return stat, err
}
stat.cpuNum, err = strconv.ParseUint(cpu[cpuIDIndex:], utils.DecimalBase, utils.Uint64Bits)
if err != nil {
err = fmt.Errorf("convert cpu number from %s failed", cpu)
return stat, err
}
return stat, nil
}
func (sysload *SystemLoad) cpusLoadUpdate(init bool) {
file, err := os.Open(utils.ProcDir + "stat")
if err != nil {
log.Error(err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
now := time.Now()
for scanner.Scan() {
line := scanner.Text()
if line[0:cpuIDIndex] != "cpu" {
break
}
if line[cpuIDIndex] == ' ' {
continue
}
stat, err := parseCPUStatLine(line)
if err != nil {
log.Error(err)
continue
}
if init {
sysload.cpusLoad[stat.cpuNum].Init(&now, stat.user, stat.system)
sysload.cpusLoad[stat.cpuNum].set(stat.cpuNum)
} else {
sysload.cpusLoad[stat.cpuNum].Update(&now, stat.user, stat.system)
}
}
file.Close()
}
type taskStat struct {
user uint64
system uint64
}
func parseTaskStatLine(str string) (taskStat, error) {
const taskUserTimeIndex = 13
const taskSystemTimeIndex = 14
var stat taskStat
var err error
statFields := strings.Fields(str)
stat.user, err = strconv.ParseUint(statFields[taskUserTimeIndex], utils.DecimalBase, utils.Uint64Bits)
if err != nil {
err = fmt.Errorf("convert task user time from %s failed", statFields[taskUserTimeIndex])
return stat, err
}
stat.system, err = strconv.ParseUint(statFields[taskSystemTimeIndex], utils.DecimalBase, utils.Uint64Bits)
if err != nil {
err = fmt.Errorf("convert task system time from %s failed", statFields[taskSystemTimeIndex])
return stat, err
}
return stat, nil
}
func getPidFromTid(tid uint64) (uint64, error) {
var pid uint64
taskStatusPath := fmt.Sprintf(utils.ProcDir+"%d/status", tid)
file, err := os.Open(taskStatusPath)
if err != nil {
return pid, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "Tgid") {
_, err := fmt.Sscanf(line, "Tgid: %d", &pid)
return pid, err
}
}
return pid, fmt.Errorf("can not find Tgid from status file")
}
func updateTaskLoad(load *Load, init bool) {
pid, err := getPidFromTid(load.id)
if err != nil {
log.Warn(err)
return
}
taskStatPath := fmt.Sprintf(utils.ProcDir+"%d/task/%d/stat", pid, load.id)
line := utils.ReadAllFile(taskStatPath)
if line == "" {
return
}
stat, err := parseTaskStatLine(line)
if err != nil {
log.Error(err)
return
}
now := time.Now()
if init {
load.Init(&now, stat.user, stat.system)
} else {
load.Update(&now, stat.user, stat.system)
}
}
func (sysload *SystemLoad) tasksLoadUpdate() {
for task := sysload.tasksLoad.Front(); task != nil; task = task.Next() {
updateTaskLoad((task.Value).(*Load), false)
}
}
// Init of SystemLoad init cpus' and tasks' load with current timestamp
func (sysload *SystemLoad) Init() {
sysload.cpusLoad = make([]Load, utils.CPUNum)
sysload.tasksLoad.Init()
sysload.cpusLoadUpdate(true)
}
// AddTask of SystemLoad monitor new task's load
func (sysload *SystemLoad) AddTask(id uint64) {
var taskLoad Load
taskLoad.id = id
updateTaskLoad(&taskLoad, true)
sysload.tasksLoad.PushBack(&taskLoad)
}
// RemoveTask of SystemLoad stop to monitor new task's load
func (sysload *SystemLoad) RemoveTask(id uint64) {
taskLoad := sysload.findTask(id)
if taskLoad != nil {
sysload.tasksLoad.Remove(taskLoad)
}
}
// Update of SystemLoad update load if cpus and monitored tasks
func (sysload *SystemLoad) Update() {
sysload.cpusLoadUpdate(false)
sysload.tasksLoadUpdate()
}
func (sysload *SystemLoad) findTask(id uint64) *list.Element {
for taskLoad := sysload.tasksLoad.Front(); taskLoad != nil; taskLoad = taskLoad.Next() {
load, ok := (taskLoad.Value).(*Load)
if !ok {
continue
}
if load.id == id {
return taskLoad
}
}
return nil
}
// GetCPULoad of SystemLoad get cpu load
func (sysload *SystemLoad) GetCPULoad(cpu int) int {
if cpu > len(sysload.cpusLoad) {
return 0
}
return sysload.cpusLoad[cpu].usage.load
}
// GetTaskLoad of SystemLoad get task load
func (sysload *SystemLoad) GetTaskLoad(id uint64) int {
taskLoad := sysload.findTask(id)
if taskLoad != nil {
return (taskLoad.Value).(*Load).usage.load
}
return 0
}
// NewSysload alloc a new sysload object
func NewSysload() *SystemLoad {
var sysload SystemLoad
sysload.Init()
return &sysload
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
package sysload
import (
"fmt"
"math/rand"
"os"
"testing"
"time"
"gitee.com/wisdom-advisor/common/testlib"
"gitee.com/wisdom-advisor/common/utils"
)
const userHz = 100
const tickLen = nsecPerSec / userHz
const tickLimit = int64((1<<64 - 1) / tickLen)
const constZero = 0
const defCPU = 1
const testCount = 10
const pidLimit = 536870912
const testCountGet = 10
const testSleepTime = 1
const percentLimit = 100
const acceptMarginPct = 3
const testLoadTaskNum = 1
// TestParseCPUStatLine test the parsing of CPU stat lines
func TestParseCPUStatLine(t *testing.T) {
cpuNum := uint64(defCPU)
var testStat testlib.CPUProcStat
testStat.Init(cpuNum)
stat, err := parseCPUStatLine(testStat.ToString())
if err != nil {
t.Errorf("parse line error which is not expected")
}
if stat.user != testStat.User {
t.Errorf(" user %d; expected %d", stat.user, testStat.User)
}
if stat.system != testStat.System {
t.Errorf(" system %d; expected %d", stat.system, testStat.System)
}
if stat.cpuNum != testStat.CPUNum {
t.Errorf(" cpuNum %d; expected %d", stat.cpuNum, testStat.CPUNum)
}
}
// TestMultiParseStat is multiple test for TestParseCPUStatLine
func TestMultiParseStat(t *testing.T) {
for i := 0; i < testCount; i++ {
t.Run("TestParseCPUStatLine", TestParseCPUStatLine)
}
}
// TestGetPidFromTid test get pid from tid
func TestGetPidFromTid(t *testing.T) {
utils.ProcDir = "./tmp/proc/"
var tid = uint64(rand.Int63n(pidLimit))
var pid = uint64(rand.Int63n(pidLimit))
path := fmt.Sprintf("./tmp/proc/%d/", tid)
rand.Seed(time.Now().Unix())
data := []byte("Name: sedispatch\n")
data = append(data, []byte("Umask: 0022\n")...)
data = append(data, []byte("State: S (sleeping)\n")...)
data = append(data, []byte(fmt.Sprintf("Tgid: %d\n", pid))...)
data = append(data, []byte("Ngid: 0\n")...)
data = append(data, []byte(fmt.Sprintf("Pid: %d\n", tid))...)
data = append(data, []byte("PPid: 1999\n")...)
data = append(data, []byte("TracerPid: 0\n")...)
data = append(data, []byte("Uid: 0 0 0 0\n")...)
data = append(data, []byte("Gid: 0 0 0 0\n")...)
data = append(data, []byte("FDSize: 64\n")...)
data = append(data, []byte("Groups: \n")...)
data = append(data, []byte("NStgid: 2002\n")...)
data = append(data, []byte("NSpid: 2002\n")...)
data = append(data, []byte("NSpgid: 1999\n")...)
data = append(data, []byte("NSsid: 1999\n")...)
data = append(data, []byte("VmPeak: 8028 kB\n")...)
data = append(data, []byte("VmSize: 8028 kB\n")...)
data = append(data, []byte("VmLck: 0 kB\n")...)
data = append(data, []byte("VmPin: 0 kB\n")...)
data = append(data, []byte("VmHWM: 3192 kB\n")...)
data = append(data, []byte("VmRSS: 3044 kB\n")...)
data = append(data, []byte("RssAnon: 404 kB\n")...)
data = append(data, []byte("RssFile: 2640 kB\n")...)
data = append(data, []byte("RssShmem: 0 kB\n")...)
data = append(data, []byte("VmData: 380 kB\n")...)
data = append(data, []byte("VmStk: 132 kB\n")...)
data = append(data, []byte("VmExe: 12 kB\n")...)
data = append(data, []byte("VmLib: 5480 kB\n")...)
data = append(data, []byte("VmPTE: 52 kB\n")...)
data = append(data, []byte("VmSwap: 0 kB\n")...)
data = append(data, []byte("HugetlbPages: 0 kB\n")...)
data = append(data, []byte("CoreDumping: 0\n")...)
data = append(data, []byte("Threads: 1\n")...)
data = append(data, []byte("SigQ: 1/13293\n")...)
data = append(data, []byte("SigPnd: 0000000000000000\n")...)
data = append(data, []byte("ShdPnd: 0000000000000000\n")...)
data = append(data, []byte("SigBlk: 0000000000000000\n")...)
data = append(data, []byte("SigIgn: fffffffe7ffabefe\n")...)
data = append(data, []byte("SigCgt: 0000000180004001\n")...)
data = append(data, []byte("CapInh: 0000000000000000\n")...)
data = append(data, []byte("CapPrm: 0000000000000000\n")...)
data = append(data, []byte("CapEff: 0000000000000000\n")...)
data = append(data, []byte("CapBnd: 0000000000000000\n")...)
data = append(data, []byte("CapAmb: 0000000000000000\n")...)
data = append(data, []byte("NoNewPrivs: 0\n")...)
data = append(data, []byte("Seccomp: 2\n")...)
data = append(data, []byte("Speculation_Store_Bypass: unknown\n")...)
data = append(data, []byte("Cpus_allowed: f\n")...)
data = append(data, []byte("Cpus_allowed_list: 0-3\n")...)
data = append(data, []byte("Mems_allowed: 01\n")...)
data = append(data, []byte("Mems_allowed_list: 0\n")...)
data = append(data, []byte("voluntary_ctxt_switches: 27663\n")...)
data = append(data, []byte("nonvoluntary_ctxt_switches: 3\n")...)
testlib.BuildFakePathWithData(path, "status", data)
res, err := getPidFromTid(tid)
if err != nil {
t.Errorf("get pid error which is not expected")
}
if res != pid {
t.Errorf(" pid %d; expected %d", res, pid)
}
errDel := os.RemoveAll("./tmp/")
if errDel != nil {
fmt.Println("Remove path error")
}
}
// TestMultiGetPid is multiple test for getPidFromTid
func TestMultiGetPid(t *testing.T) {
for i := 0; i < testCountGet; i++ {
t.Run("TestGetPidFromTid", TestGetPidFromTid)
}
}
func checkLoadValid(load int, testLoad int, t *testing.T) {
var diff int
scaleTestLoad := int(scaleUp(uint64(testLoad)) / testlib.ConvertPct)
if scaleTestLoad > load {
diff = scaleTestLoad - load
} else {
diff = load - scaleTestLoad
}
if diff > int(scaleUp(acceptMarginPct)/testlib.ConvertPct) {
t.Errorf("get load %d, scaletestLoad %d, excced accept margin %d\n", load, scaleTestLoad, acceptMarginPct)
}
}
// TestGetCPULoad test GetCPULoad
func TestGetCPULoad(t *testing.T) {
var sysload SystemLoad
topoStub, cpuLoadStub, _ := testlib.InitStub()
sysload.Init()
time.Sleep(time.Duration(testSleepTime) * time.Second)
testLoad := rand.Intn(percentLimit)
cpuLoadStub.AddLoad(topoStub.CPUNum-1, testLoad, testSleepTime)
cpuLoadStub.DeployLoad()
sysload.Update()
load := sysload.GetCPULoad(topoStub.CPUNum - 1)
checkLoadValid(load, testLoad, t)
testlib.CleanStub()
}
// TestGetTaskLoad test GetTaskLoad
func TestGetTaskLoad(t *testing.T) {
var sysload SystemLoad
_, _, taskStub := testlib.InitStub()
tids := taskStub.CreateTasks(testLoadTaskNum)
sysload.Init()
sysload.AddTask(tids[0])
time.Sleep(time.Duration(testSleepTime) * time.Second)
testLoad := rand.Intn(percentLimit)
taskStub.AddLoad(tids[0], int(testLoad), testSleepTime)
sysload.Update()
load := sysload.GetTaskLoad(tids[0])
checkLoadValid(load, testLoad, t)
testlib.CleanStub()
}
此差异已折叠。
此差异已折叠。
此差异已折叠。
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
package topology
import (
"fmt"
"math/rand"
"os"
"testing"
"time"
"gitee.com/wisdom-advisor/common/cpumask"
"gitee.com/wisdom-advisor/common/testlib"
"gitee.com/wisdom-advisor/common/utils"
)
const testChipNum = 1
func init() {
rand.Seed(time.Now().Unix())
}
// TestSelectLighterBindNode is to test SelectLighterBindNode
func TestSelectLighterBindNode(t *testing.T) {
var mask cpumask.Cpumask
utils.SysDir = "./tmp/sys/"
topoStub := testlib.NewTopoStub(testChipNum, "./tmp/sys/")
utils.CPUNum = topoStub.CPUNum
if err := InitTopo(); err != nil {
t.Errorf("InitTopo failed\n")
}
testCPUNum := rand.Intn(topoStub.CPUNum)
mask.Set(testCPUNum)
testCPUNode := findTypeTopoNode(TopoTypeCPU, &mask)
testCPUNode.SubBind()
testCPUNode = tree.root.SelectLighterBindNode(TopoTypeCPU)
if testCPUNode.id != testCPUNum {
t.Errorf("expect cpu id %d, result %d\n", testCPUNum, testCPUNode.id)
}
err := os.RemoveAll("./tmp/")
if err != nil {
fmt.Println("Remove path error")
}
}
/*
* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* wisdom-advisor is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-6-9
*/
// Package utils provides some commonly used methods
package utils
import (
"bufio"
"errors"
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
)
// const for function ParseXxx
const (
DecimalBase = 10
HexBase = 16
Uint64Bits = 64
cpuNumStartIndex = 4
)
// ProcDir is path of proc sysfs
var ProcDir = "/proc/"
// SysDir is path of sysfs
var SysDir = "/sys/"
// CPUNum is cpu number in system
var CPUNum int
// SysCPUPath is the path of CPU related sysfs
var SysCPUPath = "/sys/devices/system/cpu"
var cpuInfoFilePath = "/proc/cpuinfo"
var processorIdentifier = "processor"
func getPhysicalCPUNumber() int {
f, err := os.Open(cpuInfoFilePath)
if err != nil {
return 0
}
defer f.Close()
cpuNum := 0
s := bufio.NewScanner(f)
for s.Scan() {
if err := s.Err(); err != nil {
return 0
}
fields := strings.Fields(s.Text())
if len(fields) > 0 {
if fields[0] == processorIdentifier {
cpuNum++
}
}
}
return cpuNum
}
func init() {
CPUNum = getPhysicalCPUNumber()
}
// ReadAllFile read all file to string.
func ReadAllFile(path string) string {
content, err := ioutil.ReadFile(path)
if err != nil {
return ""
}
return string(content)
}
// IsFileExisted indicates whether the file exists or not
func IsFileExisted(path string) bool {
_, err := os.Stat(path)
if err != nil && !os.IsExist(err) {
return false
}
return true
}
// GetPid get the pid of the process with specified comm
func GetPid(name string) (uint64, error) {
files, err := ioutil.ReadDir(ProcDir)
if err != nil {
return 0, errors.New("cannot get pid")
}
for _, file := range files {
tid, err := strconv.ParseUint(file.Name(), DecimalBase, Uint64Bits)
if err != nil {
continue
}
data, err := ioutil.ReadFile(ProcDir + file.Name() + "/comm")
if err != nil {
continue
}
if name == strings.Replace(string(data), "\n", "", -1) {
return tid, nil
}
}
return 0, errors.New("cannot get pid")
}
// GetCPUNumaID is to get the NUMA id of specified CPU
func GetCPUNumaID(cpu int) int {
var node = -1
var err error
cpuDirPath := fmt.Sprintf("%s/cpu%d/", SysCPUPath, cpu)
dir, err := ioutil.ReadDir(cpuDirPath)
if err != nil {
return -1
}
for _, f := range dir {
if strings.HasPrefix(f.Name(), "node") {
node, err = strconv.Atoi(f.Name()[cpuNumStartIndex:])
if err != nil {
node = -1
}
break
}
}
return node
}
- master: 主线分支,合入bugfix、特性,支撑项目演进
\ No newline at end of file
此差异已折叠。
.PHONY: all
X86_64=x86_64
ARRCH64=aarch64
CC:=gcc
CFLAGS:=-Wall
all: sem net_test
sem:
$(CC) $(CFLAGS) sem.c -o sem -lpthread
net_test:
$(CC) $(CFLAGS) net_test.c -o net_test
clean:
rm sem
rm net_test
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册