type any added in go1.18

type any = interface{}
any is an alias for interface{} and is equivalent to interface{} in all ways

any是空接口的别名,在go1.18后引入

接口是两件事物:它是一组方法,也是一种类型。
interface{} 类型是没有方法的接口。由于没有 implements 关键字,所有类型都至少实现零个方法,并且自动满足接口,所有类型都满足空接口,所以空接口能够被用来存储任意类型的值


inerface{}底层实现

type emptyInterface struct {
   typ  *rtype            // 类型描述
   Word unsafe.Pointer    // 值
}

可以看到其包含一个类型描述和一个值

type rtype struct {
   size       uintptr
   ptrdata    uintptr
   hash       uint32
   tflag      tflag
   align      uint8
   fieldAlign uint8
   kind       uint8
   alg        *typeAlg
   gcdata     *byte
   str        nameOff
   ptrToThis  typeOff
}

空接口嵌入的类型,我们可以映射导出字段或列出方法

package main

import "fmt"

type Set map[string]struct{} //空struct不占空间

func main() {
    s := make(Set)
    s["zhangsan"] = struct{}{}
    s["lisi"] = struct{}{}
    s["wangwu"] = struct{}{}
    fmt.Println(get_value(s))
}

func get_value(s Set) []string {
    var setvalue []string
    for key, _ := range s {
        setvalue = append(setvalue, key)
    }
    return setvalue
}

map的key无序、不重复,跟set有着很相似的特性,而空struct{}不占空间,用来填充值是最合适的,struct{}{}是一个复合字面量,他表示一个struct{}类型的值,因为是字面量(除了数字以外的常量),所以所有struct{}{}指向同一个位置

基础介绍

go语言圣经开源翻译版

go是一门编译型语言

go通过package组织,一个包由单个目录下一个或者多个go源代码文件组成,每个源文件都以一条package声明语句开始,如package main,表示文件属于哪个包,紧跟着一系列import的包

main包比较特殊,它定义了一个独立可执行的程序而不是一个库,main函数则是程序执行的入口(也许是继承了早期开发时c语言的风格

go不需要用分号换行

package main

import "fmt"

func main() { /*go的左大括号不能单独放在一行,必须以这种一半换行一半不换行的风格写括号,太野蛮了!太野蛮了!*/
    fmt.Println("Hello,golang")
}

作为一门编译型语言,当然少不了编译运行。golang提供了go这个工具配合不同指令对你所编写的工程文件进行操作

build:这个指令会把你的工程文件编译成一个二进制可执行文件(在win里面它是exe)

go build example.go 
./example 
Hello,golang

run:当然,你也可以直接运行

go run example.go 
Hello,golang

占位符

example
type Human struct {
    Name string
}

var people = Human{Name:"zhangsan"}

//////////////////////////////

%v      相应值的默认格式。            Printf("%v", people)   {zhangsan},
%+v     打印结构体时,会添加字段名     Printf("%+v", people)  {Name:zhangsan}
%#v     相应值的Go语法表示            Printf("#v", people)   main.Human{Name:"zhangsan"}
%T      相应值的类型的Go语法表示       Printf("%T", people)   main.Human
%%      字面上的百分号,并非值的占位符  Printf("%%")            %

%t          true 或 false。     Printf("%t", true)       true

%s      输出字符串表示(string类型或[]byte)   Printf("%s", []byte("Go语言"))  Go语言
%q      双引号围绕的字符串,由Go语法安全地转义  Printf("%q", "Go语言")         "Go语言"
%x      十六进制,小写字母,每字节两个字符      Printf("%x", "golang")         676f6c616e67
%X      十六进制,大写字母,每字节两个字符      Printf("%X", "golang")         676F6C616E67

%b      无小数部分的,指数为二的幂的科学计数法,
        与 strconv.FormatFloat 的 'b' 转换格式一致。例如 -123456p-78
%e      科学计数法,例如 -1234.456e+78        Printf("%e", 10.2)     1.020000e+01
%E      科学计数法,例如 -1234.456E+78        Printf("%e", 10.2)     1.020000E+01
%f      有小数点而无指数,例如 123.456        Printf("%f", 10.2)     10.200000
%g      根据情况选择 %e 或 %f 以产生更紧凑的(无末尾的0)输出 Printf("%g", 10.20)   10.2
%G      根据情况选择 %E 或 %f 以产生更紧凑的(无末尾的0)输出 Printf("%G", 10.20+2i) (10.2+2i)

%b      二进制表示                             Printf("%b", 5)             101
%c      相应Unicode码点所表示的字符              Printf("%c", 0x4E2D)        中
%d      十进制表示                             Printf("%d", 0x12)          18
%o      八进制表示                             Printf("%d", 10)            12
%q      单引号围绕的字符字面值,由Go语法安全地转义 Printf("%q", 0x4E2D)        '中'
%x      十六进制表示,字母形式为小写 a-f         Printf("%x", 13)             d
%X      十六进制表示,字母形式为大写 A-F         Printf("%x", 13)             D
%U      Unicode格式:U+1234,等同于 "U+%04X"   Printf("%U", 0x4E2D)         U+4E2D

基础语法

前排提醒,go里面的单引号是字符(byte,rune),字符串只能用双引号

变量、格式化输出、常量

go的变量以var开始,可以是

var age int /*没说明值默认为0,bool为false,字符串为空串,指针等为nil*/
var b bool = true
var identifier1, identifier2 type
var (
    vname1 v_type1
    vname2 v_type2
)

也可以是

var age = 19

或者也可以不用var声明,采用赋值符:=,但是这只能用在函数内部,不能用于全局变量的申明

number1, number2, number3 := 1, 2, 3
a :=[7]int{1,2,3,4,5,6,7} //go的数组长得真奇怪
q := [...]int{1, 2, 3}

所有被申明的局部变量必须被使用,如果存在未使用的变量会报错

go可以使用fmt.sprintf来格式化语句,然后再用printf来输出,或者直接printf

package main

import "fmt"

func main() {
    var name = "zhangsan"
    var age = 19
    var words = "i'm %s,%d years old"
    var format_output = fmt.Sprintf(words, name, age)
    fmt.Println(format_output)
}
package main

import "fmt"

func main() {
    var name = "zhangsan"
    var age = 19
    fmt.Printf("i'm %s,%d years old", name, age)
}
td@tddeMacBook-Air blog % go run example.go
i'm zhangsan,19 years old

Println :可以打印出字符串,和变量

Printf : 只可以打印出格式化的字符串,可以输出字符串类型的变量,不可以输出整型变量和整型

Sprintf:按照传入的格式化规则符将传入的变量格式化,(终端中不会有显示,即不会有信息输出在控制台),返回为 格式化后的字符串

讲完变量自然也有常量

常量的命名格式为const identifier [type] = value,类型可以不加,让编译器自己去判断

const LENGTH int = 10
const (
    Unknown = 0
    Female = 1
    Male = 2
)

iota是一个特殊的常量,他第一次出现时被置为0,后面每出现一次+1

const (
    a = iota
    b = iota
    c = iota
)
//a=0, b=1, c=2 
const (
    a = iota
    b
    c
)

复合数据类型

数组

跟其他语言的很像,但是要写死长度,内置len

var a [3]int             // array of 3 integers
fmt.Println(a[0])        // print the first element
fmt.Println(a[len(a)-1]) // print the last element, a[2]

// Print the indices and elements.
for i, v := range a {
    fmt.Printf("%d %d\n", i, v)
}

// Print the elements only.
for _, v := range a {
    fmt.Printf("%d\n", v)
}

q := [...]int{1, 2, 3} //表示长度由具体的值来决定

数组也可以用索引的方式来表达

type Currency int

const (
    USD Currency = iota // 美元
    EUR                 // 欧元
    GBP                 // 英镑
    RMB                 // 人民币
)

symbol := [...]string{USD: "$", EUR: "€", GBP: "£", RMB: "¥"}

fmt.Println(RMB, symbol[RMB]) // "3 ¥"


r := [...]int{99: -1} //这是个含有100个元素的数组,除了第一百个元素被赋值为-1,其他都为默认值0

切片

尽管数组可以用指针来表示,但是他依旧是值类型的,而且写死的长度会使的他缺少灵活性,所以数组一般不会被用作于函数的参数,而是使用指针类型的切片来替代

切片的底层其实也是一个数组对象,但他没有固定长度。一个slice由指针、长度和容量组成,指针指向第一个切片元素对应的底层数组的元素地址,但是切片的第一个元素并不一定是数组的第一个元素,长度对应来元素的数目,长度不能超过容量,len和cap分别能返回切片的长度和容量。使用append可以添加元素,当添加使的长度超过容量时,那么会分配另外一块更长的内存空间给他,并把原来的数据复制过去

多个切片之间可以共享底层的数据

slice的切片操作可以用s[start:stop]来实现,s[:]表示全部,和数组一样。因为slice包含指针,所以他允许函数修改内部的值

slice只能和nil比较,不能互相比较,而数组可以

打印时可以使用nonempty()来忽略默认值

var b1 []int  //和数组长得很像,但是不定长
b2 := make([]int, 3, 5) //使用make来定义,参数为类型、长度、容量,类型和长度不可缺省
import "fmt"

func main() {
    var a []string
    b := []int{1, 2, 3, 4, 5}
    fmt.Print("a", cap(a), len(a))
    fmt.Print("b", cap(b), len(b))
    a = append(a, "123", "321", "sss")
    b = append(b, 6, 7)
    fmt.Print(a)
    fmt.Print("a", cap(a), len(a))
    fmt.Print("b", cap(b), len(b))
}

//a0 0b5 5[123 321 sss]a3 3b10 7%

Map哈希表

map是一个哈希表,他是一个无序的键值对集合,通过key来访问对应的values,不过map中的元素不是一个变量,因此不能对map的元素进行取址操作.其零值也是nil,map间不能互相比较,只能和nil比较,如果想要比较两个map,得用循环实现。map的key必须是可比较类型

//type1
ages := make(map[string]int)
//type2
ages := map[string]int{
    "alice":   31,
    "charlie": 34,
}
//type3
ages := make(map[string]int)
ages["alice"] = 31
ages["charlie"] = 34

delete(ages, "alice")//删除元素

因为map是无序的,所以每一次遍历的顺序也不同,如果想要按照一定的顺序去遍历,那么必须使用sort先对key排序

import "sort"

var names []string
for name := range ages {
    names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
    fmt.Printf("%s\t%d\n", name, ages[name])
}

结构体

梦回c系列
go中所有函数参数都是值拷入,如果想让函数能够修改结构体的值,那么必须用指针传入
结构体可以比较

//type1
type Employee struct {
    ID        int
    Name      string
    Address   string
    DoB       time.Time
    Position  string
    Salary    int
    ManagerID int
}
//首字母大写的成员是导出的,在JSON中,只有导出的结构体对象会被编码
var dilbert Employee

dilbert.Salary -= 5000

position := &dilbert.Position
*position = "Senior " + *position

var employeeOfTheMonth *Employee = &dilbert
employeeOfTheMonth.Position += " (proactive team player)"


//type2
type Point struct{ X, Y int }

p := Point{1, 2}

//type3
type Point struct{ X, Y int }

p := Point{X: 1, Y: 2}
fmt.Print(p)

go没有类,不过结构体可以使用匿名成员来使用别的结构体。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针

type Point struct {
    X, Y int
}
type Circle struct {
    Point
    Radius int
}

type Wheel struct {
    Circle
    Spokes int
}

var w Wheel
w.X = 8            // equivalent to w.Circle.Point.X = 8
w.Y = 8            // equivalent to w.Circle.Point.Y = 8
w.Radius = 5       // equivalent to w.Circle.Radius = 5
w.Spokes = 20
//在包外部,由于circle和point没有导出,所以不能用简短的方式来访问这些匿名成员的属性
//我们可以以以上的方式直接去访问匿名成员的属性,不过如果我们想要实例化一个,就只能按照下面的语法来

w := Wheel{Circle{Point{8, 8}, 5}, 20}

w := Wheel{
    Circle: Circle{
        Point:  Point{X: 8, Y: 8},
        Radius: 5,
    },
    Spokes: 20, // NOTE: trailing comma necessary here (and at Radius)
}

    fmt.Print(w, "\n")
    fmt.Printf("%v\n", w)
    fmt.Printf("%#v\n", w) //#修饰符会使得输出所有成员的名字
    // Output:
    // Wheel{Circle:Circle{Point:Point{X:8, Y:8}, Radius:5}, Spokes:20}

    w.X = 42

    fmt.Printf("%#v\n", w)

// {{{8 8} 5} 20}
// {{{8 8} 5} 20}
// main.Wheel{Circle:main.Circle{Point:main.Point{X:8, Y:8}, Radius:5}, Spokes:20}
// main.Wheel{Circle:main.Circle{Point:main.Point{X:42, Y:8}, Radius:5}, Spokes:20}

判断

if

if 布尔表达式 {
   /* 在布尔表达式为 true 时执行 */
} else {
  /* 在布尔表达式为 false 时执行 */
}

switch

 switch var1 {
    case val1:
        ...
    case val2:
        ...
    default:
        ...
}

select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的,如果没有case可运行,也没有default,select将会阻塞,直到某个case可以运行

select {
    case communication clause  :
       statement(s);      
    case communication clause  :
       statement(s);
    /* 你可以定义任意数量的 case */
    default : /* 可选 */
       statement(s);
}

循环

go没有while,只有for,但是可以达到while的效果

10内加减循环

package main

import "fmt"

func main() {
   sum := 0
      for i := 0; i <= 10; i++ {
         sum += i
      }
   fmt.Println(sum)
}


// 类似while风格
package main

import "fmt"

func main() {
   sum := 1
   for ; sum <= 10; {
      sum += sum
   }
   fmt.Println(sum)

   // 这样写也可以,更像 While 语句形式
   for sum <= 10{
      sum += sum
   }
   fmt.Println(sum)
}

无限循环

package main

import "fmt"

func main() {
   sum := 0
   for {
      sum++ // 无限循环下去
   }
   fmt.Println(sum) // 无法输出
}

使用range关键字来对可迭代对象(如数组和map)进行循环输出(index和内容

func main() {
    a := [3]int{1, 2, 3}
    for t, b := range a {
        fmt.Println(t, b)
    }
}

//省略index或者value

package main
import "fmt"

func main() {
    map1 := make(map[int]float32)
    map1[1] = 1.0
    map1[2] = 2.0
    map1[3] = 3.0
    map1[4] = 4.0
   
    // 读取 key 和 value
    for key, value := range map1 {
      fmt.Printf("key is: %d - value is: %f\n", key, value)
    }

    // 读取 key
    for key := range map1 {
      fmt.Printf("key is: %d\n", key)
    }

    // 读取 value,使用废弃占位符
    for _, value := range map1 {
      fmt.Printf("value is: %f\n", value)
    }
}

//map是一种无序键值对结构,map[KeyType]ValueType,默认初始值为nil,需要使用make()来初始化

//直接赋值
import "fmt"

func main() {
a := map[string]string{"key":"嗨","value":"山石"}
    for t,b := range a{
        fmt.Println(t,b)
       }
    }


//make初始化
package main

import "fmt"

func main() {
    var a map[string]int
    a = make(map[string]int)
    a["张三"] = 1
    for t, b := range a {
        fmt.Println(t, b)
    }
}

go也拥有break

函数

拥有函数名的函数只能在包级语块中被定义

func function_name( [parameter list] ) [return_types] {
   函数体
}

一般来说函数有两个返回值,一个是期望的返回值,一个是错误信息,error类型本身是一个接口类型

example

package main

import (
    "fmt"
)

// 定义一个 DivideError 结构
type DivideError struct {
    dividee int
    divider int
}

// 实现 `error` 接口
func (de *DivideError) Error() string {
    strFormat := `
    Cannot proceed, the divider is zero.
    dividee: %d
    divider: 0
`
    return fmt.Sprintf(strFormat, de.dividee)
}

// 定义 `int` 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
    if varDivider == 0 {
        dData := DivideError{
            dividee: varDividee,
            divider: varDivider,
        }
        errorMsg = dData.Error()
        return
    } else {
        return varDividee / varDivider, ""
    }

}

func main() {

    // 正常情况
    if result, errorMsg := Divide(100, 10); errorMsg == "" {
        fmt.Println("100/10 = ", result)
    }
    // 当除数为零的时候会返回错误信息
    if _, errorMsg := Divide(100, 0); errorMsg != "" {
        fmt.Println("errorMsg is: ", errorMsg)
    }
}


// go run example.go
// 100/10 =  10
// errorMsg is:  
//     Cannot proceed, the divider is zero.
//     dividee: 100
//     divider: 0

可变参数:...代表会接受任意数量这个类型的参数

func sum(vals ...int) int {
    total := 0
    for _, val := range vals {
        total += val
    }
    return total
}

匿名函数:没有名字的函数,可以在函数体里面定义调用

func(参数列表)(返回参数列表){
    函数体
}


f := func(data int) {
   fmt.Println("hello", data)
}
f(100) //调用

接口

go没有类,但是采用了一种奇怪的方式将结构体和函数联系了起来

接口又称为动态数据类型,在进行接口使用的的时候,会将接口对位置的动态类型改为所指向的类型

会将动态值改成所指向类型的结构体

语法

type interface_name interface {  //定义一个接口
   method_name1 [return_type]
   method_name2 [return_type]
   method_name3 [return_type]
   ...
   method_namen [return_type]
}

/* 定义结构体 */
type struct_name struct {
   /* variables */
}

/* 实现接口方法,方法和函数的区别在于方法在方法名前面限定了实现的数据结构,可以通过实例名访问该实例的字段name和其他方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
   /* 方法实现 */
}//只传值,不能改变内部的数据
...
func (struct_name_variable *struct_name) method_namen() [return_type] {
   /* 方法实现*/
}//指针接收,可以改变值

例子

package main

import (
    "fmt"
)

type Phone interface {
    call()
}

type NokiaPhone struct {
}

func (nokiaPhone NokiaPhone) call() {
    fmt.Println("I am Nokia, I can call you!")
}

type IPhone struct {
}

func (iPhone IPhone) call() {
    fmt.Println("I am iPhone, I can call you!")
}

func main() {
    var phone Phone

    phone = new(NokiaPhone)
    phone.call()

    phone = new(IPhone)
    phone.call()

}

线程、通道

go可以使用关键字go来开启轻量级线程goroutine,goroutine的调度由go运行时决定

语法

go example(x,y,z)

通道能够用来在两个线程之间传递数据

ch := make(chan int)//申明
ch <- v    // 把 v 发送到通道 ch
v := <-ch  // 从 ch 接收数据
           // 并把值赋给 v
ch := make(chan int, 100)//设置缓冲区,如果没有缓冲区,发送方会阻塞直到接收方从通道接收到了值,如果带缓冲,则会阻塞到发送的值被拷贝到缓冲区内。接收方在有值可以接收之前一直会阻塞

example

package main

import (
    "fmt"
)

var count int = 0

func sum(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
        fmt.Print("v,sum", v, sum, "\n")
        fmt.Printf("this is %d \n", count)
        count += 1
    }
    c <- sum // 把 sum 发送到通道
    fmt.Print(sum, "\n")
}

func main() {
    s := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    go sum(s[:len(s)/2], c)
    go sum(s[len(s)/2:], c)
    // x := <-c
    // fmt.Print(x)
    x, y := <-c, <-c // 从通道 c 中接收

    fmt.Println("x,y,x+y:", x, y, x+y)
}

测试新写的snmp脚本,线上环境一切ok,线下环境一个都获取不到数据,于是乎开始检查
1. 检查snmp环境和服务
新装的,服务起了,命令也有
2.检查是不是oid之类的问题
跑脚本检活不通,直接跑语句,显示Timeout: No Response from xxxx
因为用的是厂商定制的oid,所以不确定是不是oid的问题(环境里面有很多种不同的厂商),于是随便找了个通用的oid试了下,提示snmpwalk: Timeout (Sub-id not found: (top) -> )
那应该不是语句的问题了
3.检查是否因为网络原因
snmp基于udp协议访问161端口,用nmap扫之
nmap xxxxxx -sU -p 161
先试了下一台能正常接收到snmp数据的
Host is up (0.0013s latency).
PORT STATE SERVICE
161/udp open|filtered snmp

再扫一下有问题的一台机子

Host is up (0.0011s latency).
PORT STATE SERVICE
161/udp closed snmp

嗯,那就是网络的问题了,找管网络的开通一下

(持续更新中。。。

跟同事排了一天原因,现在目前找到以下几种可能:

  1. 带外线没接/没接好
  2. bmc有故障
  3. 混杂了几台不同机型的机子,mib不同,得去跟售后扯皮(dell能直接下,其他的就。。。
  4. 防火墙
  5. 不同机房环境不通
  6. snmp没装/有问题

目前还出现了一些手动跑脚本/命令有数据,但是监控就是死活获取不到的情况,排查ing

另外出现一种情况,一些机子一直报掉盘,但是查看历史数据好像一切正常,考虑到之前观察监控的情况,有些机子可能五分钟数据就上来了,有些机子就要等个几十分钟或者一两个小时,猜想有可能后面那类机子的网络有点问题,时通时不通,监控拿不到数据的情况下按0处理,和前面正常数据一减就变成负的触发了告警。这个要么想办法排查网络问题要么可以考虑把脚本逻辑换成监测坏盘的数量

观察发现有些机子获取不到的原因可能是发送的信息太多卡住了,需要重启下带外

dell售后支持
https://www.dell.com/support/home/zh-cn
https://www.dell.com/support/kbdoc/zh-cn/000177052/

介绍与安装

atop是一个轻巧高效的系统资源与进程监控工具,其默认会以10s一次的频率来刷新数据,以10min一次的频率输出监控数据到日志文件

源码

https://github.com/Atoptool/atop

安装

yum install epel-release

yum install -y atop

基础页面

使用atop命令就能进入监控界面,整个页面被分隔成上下两块,上面显示的是系统资源相关的数据,下面显示的则是实时的进程信息

上界面

PRC | sys:过去10s所有进程在内核台运行的时间之和|usr:过去10所有进程在用户态运行的时间之和|#proc:进程总数 | #trun:过去10s转换的进程数|#tslpi:中断状态的睡眠进程|#tslpu:不可中断的睡眠进程|#zombie:过去10秒的僵尸进程数|clones:10s内clonde()被调用的次数|#exit 过去10秒退出的进程数量

CPU | sys:过去10s所有进程在内核台运行的时间占比|usr:过去10所有进程在用户态运行的时间占比| irq :CPU被用于处理中断的时间比例|idle:CPU空闲的时间比例 |wait: CPU处在进程等待磁盘IO,导致CPU空闲的时间比例 |steal: 为虚拟机准备的监控项,显示被运行在同一硬件上的其他虚拟机窃取的cpu时间的百分比 |guest: 为物理机准备的监控项,显示了虚拟机使用的cpu时间的百分比。 注意,这个百分比 与用户的百分比重合! |curf:当前频率

CPL | avg: 表示过去1分钟,5分钟,15分钟内运行队列中的平均进程数量 |csw: 指上下文交换次数 |intr: 指中断发生次数 |numcpu: 可用cpu数量 |

MEN | tot: 物理内存的总量 |free: 当前空闲的内存量 |cache: 用于页缓存的内存大小 |dirty: 页缓存中需要刷新到磁盘的内存量 |buff: 用于文件缓存的内存大小 |slab: 系统内核占用的内存大小|slrec: 可回收的slab内存量 |shmem: 包括tmpfs在内的共享内存的常驻大小 |shrss: 共享内存的常驻大小 |shswp: 当前交换的共享内存的数量 |numnode: 系统中NUMA节点的数量 |

SWP:|tot: 交换区总量 |free: 空闲交换空间大小 |swcac: 交换缓存的大小 |vmcom: 承诺的虚拟内存空间 |vmlim: 承诺空间的最大限制,默认情况下是交换大小加上50%的内存大小

LVM/MDD/DSK:逻辑卷/多设备/磁盘利用率 |busy: 磁盘忙时比例|write: 写请求 |read: 读请求 |discrd: 发出的丢弃请求的数量 |KiB/r: 每次读取的KiBytes数量 |kiB/w: 每次写入的KiBytes数量 |KiB/d: 每次丢弃的KiBytes数量 |Mbrs/w: 每秒读取的MiBytes吞吐量 |MBw/s: 每秒写入的MiBytes吞吐量 |avq: 平均队列深度|avio:一个请求所需的平均毫秒数

以上各项不是一直都会显示,如果内核不支持其中一些数据的话将不会被列出

NET 网络利用率| transport | tcpi :接收到的TCP段的数量,包括那些接收到的错误段| tcpo:传输的TCP段的数量,不包括那些只包含重传的八位字节 | udpi :收到的UDP数据包的数量 | udpo:传输的UDP数据包的数量 | tcpao:活动的TCP打开的数量 | tcppo :被动TCP打开次数 | tcprs :TCP输出重传次数 | tcpie:TCP输入错误次数 | tcpor:TCP输出复位次数 | udpnp:UDP无端口数 | udpie:UDP输入错误数 |

NET传输层(TCP和UDP)的活动显示一行,IP层显示一行,每个活动接口显示一行。
如果屏幕宽度不允许所有这些计数器,则只显示相关子集。

下界面

下界面显示的项会根据当前的选项发生变化,有时可能会因为屏幕大小显示不全,因可选项过多,此处只列出默认展示项的含义,更多可以使用man atop查询,当进程过多一页无法完全展示,可以用方向键上下来滚动

PID :进程PID
SYSCPU:内核态占用时间
USRCPU:用户态占用时间      
RDELAY :运行队列延迟,即在运行队列中等待的时间。
VGROW:过去10s增长的虚拟空间的大小   
RGROW:过去10s增长的内存的大小      
RDDSK:过去10s读磁盘的数据量  
WRDSK :过去10s写磁盘的数据量   
RUID :进程执行时的真实用户ID    
EUID:该进程执行的有效用户ID

ST       EXC:EXC 终止进程的退出代码(`ST'列的第二个位置是E)或致命信号的编号(`ST'列的第二个位置是S或C)        
THR :进程的线程总数
S:主线程当前状态,`R'表示运行(当前正在处理或在运行队列中),`S'表示可中断睡眠(等待事件发生),`D'表示可中断睡眠,`Z'表示不可中断。可中断,`Z'表示僵尸(等待与父进程同步),`T'表示停止(暂停或跟踪),`W'表示交换,`E'(退出)表示在最后一个间隔期间完成的进程。E'(exit)表示在最后的时间间隔内完成的进程。
      
CPUNR :该(主)线程正在运行或最近运行的CPU的标识       
CPU :这个进程的占用率与系统层面上这个资源的可用容量有关
CMD:进程的名称 ,进程的完整命令行(包括参数)。如果命令行的长度超过了屏幕行的长度,可以用方向键->和<-进行水平滚动。      

指令

显示正在活动的数字:
        'g' - 通用信息(默认)
        'm' - 显示内存信息
        'd' - 磁盘详细信息
        'n' - 网络详情
        's' - 调度和线程组信息
        'v' - 各种信息(ppid,用户/组,日期/时间,状态,exitcode)
        'c' - 每个进程的完整命令行
        'o' - 使用自己的输出行定义

按以下顺序对进程列表排序:
        'C' -  cpu活动
        'M' - 内存消耗
        'D' - 磁盘活动
        'N' - 网络活动
        'A' - 最活跃的系统资源(自动模式)

累计数字:
        'u' - 每个用户的总资源消耗
        'p' - 每个程序的总资源消耗(即相同的进程名称)

选择:
        'U' - 专注于特定用户名(正则表达式)
        'P' - 专注于特定的进程名称(正则表达式)

屏幕处理:
        ^ L  - 重绘屏幕
        ^ F  - 显示进程列表中的下一页(转发)
        ^ B  - 显示进程列表中的上一页(向后)

演示文稿(这些键显示在标题行中):
        'a' - 显示所有进程(默认:活动进程)(切换)
        'f' - 固定标题行的静态范围(切换)
        '1' - 显示平均每秒i.s.o.总值(切换)

原始文件查看:
        't' - 在原始文件中显示下一个样本
        'T' - 显示原始文件中的上一个示例
        'b' - 在原始文件中分支到特定时间)
        'r' - 回退到原始文件的开头)

其他命令:
        'i' - 更改间隔计时器(0 =仅手动触发器)
        't' - 手动触发强制下一个样本
        'r' - 将计数器重置为启动时间值
        'z' - 暂停按钮以冻结当前样本(切换)

        'l' - 每个CPU,磁盘和接口资源的限制行
        'k' - 杀死进程(即发送信号)

        'V' - 版本信息
        '?' - 帮助信息
        'h' - 帮助信息
        'q' - 退出这个程序

日志

atop会保存日志,我们可以通过 atop -r 文件名来查看日志

日志的配置文件在/usr/share/atop/atop.daily

LOGOPTS=""                              # default options
LOGINTERVAL=600                         # default interval in seconds
LOGGENERATIONS=28                       # default number of days
LOGPATH=/var/log/atop                   # default log location