├── LICENSE ├── README.md └── srvfunc ├── README.md ├── common.go ├── cpu.go ├── dialer.go ├── errors.go ├── pprof.go ├── user.go ├── user_darwin.go └── user_linux.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 VK.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # engine-go 2 | Common libraries for our go engines (microservices) 3 | -------------------------------------------------------------------------------- /srvfunc/README.md: -------------------------------------------------------------------------------- 1 | # srvfunc 2 | 3 | Библиотека общего функционала. 4 | -------------------------------------------------------------------------------- /srvfunc/common.go: -------------------------------------------------------------------------------- 1 | package srvfunc 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net" 7 | "os" 8 | "os/signal" 9 | "runtime" 10 | "syscall" 11 | "time" 12 | ) 13 | 14 | const ( 15 | pagesize = 4096 // замена C.sysconf(C._SC_PAGESIZE) 16 | ) 17 | 18 | type ( 19 | // MemStats содержит статистику по использованию памяти в байтах 20 | // @see man proc по /proc/*/statm 21 | MemStats struct { 22 | Size uint64 23 | Res uint64 24 | Share uint64 25 | Text uint64 26 | Lib uint64 27 | Data uint64 28 | Dt uint64 29 | } 30 | 31 | // GCStats содержит статистику по работе GC 32 | GCStats struct { 33 | // PauseTotalMs это общее время работы GC в миллисекундах 34 | PauseTotalMs uint64 35 | // PauseTotalMcs это общее время работы GC в микросекундах 36 | PauseTotalMcs uint64 37 | // LastPausesMs это длительность всех пауз GC в мс с прошлого вызова GetGCStats (но не более размера циклического буфера) 38 | LastPausesMs []uint64 39 | // LastPausesMcs это длительность всех пауз GC в микросекундах с прошлого вызова GetGCStats (но не более размера циклического буфера) 40 | LastPausesMcs []uint64 41 | // GCCPUFraction это процент времени (real time), потраченного на GC 42 | GCCPUFraction float64 43 | 44 | prevNumGC uint64 45 | } 46 | ) 47 | 48 | // SetMaxRLimitNoFile пробует выставить текущие nofile лимиты (ulimit -n) в максимально разрешенные 49 | // Вернет в случае успеха кортеж (cur, max) значений лимита 50 | func SetMaxRLimitNoFile() ([]uint64, error) { 51 | var rLimit syscall.Rlimit 52 | if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil { 53 | return nil, err 54 | } 55 | 56 | if rLimit.Cur < rLimit.Max { 57 | rLimit.Cur = rLimit.Max 58 | syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) 59 | } 60 | 61 | if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil { 62 | return nil, err 63 | } 64 | 65 | return []uint64{uint64(rLimit.Cur), uint64(rLimit.Max)}, nil 66 | } 67 | 68 | // GetNumOpenedFile вычисляет количество используемых файловых дескрипторов приложением 69 | // @see man proc по /proc/*/fd/ 70 | func GetNumOpenedFile(pid int) (int, error) { 71 | var path string 72 | if pid < 1 { 73 | path = `/proc/self/fd` 74 | } else { 75 | path = fmt.Sprintf(`/proc/%d/fd`, pid) 76 | } 77 | 78 | list, err := ioutil.ReadDir(path) 79 | if err != nil { 80 | return 0, err 81 | } 82 | 83 | return len(list), nil 84 | } 85 | 86 | // MakeSigChan создает канал для получения сигналов указанных типов 87 | func MakeSigChan(sig ...os.Signal) chan os.Signal { 88 | ch := make(chan os.Signal, 10) 89 | signal.Notify(ch, sig...) 90 | return ch 91 | } 92 | 93 | // GetMemStat возвращает статистику по использованию памяти 94 | // @see man proc по /proc/*/statm 95 | func GetMemStat(pid uint16) (*MemStats, error) { 96 | var fname string 97 | if pid > 0 { 98 | fname = fmt.Sprintf(`/proc/%d/statm`, pid) 99 | } else { 100 | fname = `/proc/self/statm` 101 | } 102 | 103 | fd, err := os.Open(fname) 104 | if err != nil { 105 | return nil, err 106 | } 107 | defer fd.Close() 108 | 109 | var m MemStats 110 | 111 | if _, err := fmt.Fscanf(fd, `%d %d %d %d %d %d %d`, &m.Size, &m.Res, &m.Share, &m.Text, &m.Lib, &m.Data, &m.Dt); err != nil { 112 | return nil, err 113 | } 114 | 115 | m.Size *= pagesize 116 | m.Res *= pagesize 117 | m.Share *= pagesize 118 | m.Text *= pagesize 119 | m.Lib *= pagesize 120 | m.Data *= pagesize 121 | m.Dt *= pagesize 122 | 123 | return &m, nil 124 | } 125 | 126 | // GetGCStats возвращает статистику по работе GC 127 | func GetGCStats() (stat GCStats) { 128 | var memStats runtime.MemStats 129 | runtime.ReadMemStats(&memStats) 130 | 131 | const NsecInMsec = uint64(time.Millisecond / time.Nanosecond) 132 | const NsecInMcsec = uint64(time.Microsecond / time.Nanosecond) 133 | 134 | stat.PauseTotalMs = uint64(memStats.PauseTotalNs / NsecInMsec) 135 | stat.PauseTotalMcs = uint64(memStats.PauseTotalNs / NsecInMcsec) 136 | 137 | stat.GCCPUFraction = 100 * memStats.GCCPUFraction 138 | 139 | mod := uint64(len(memStats.PauseNs)) 140 | numGC := uint64(memStats.NumGC) % mod 141 | 142 | for { 143 | pauseMs := memStats.PauseNs[stat.prevNumGC] / NsecInMsec 144 | pauseMcs := memStats.PauseNs[stat.prevNumGC] / NsecInMcsec 145 | stat.LastPausesMs = append(stat.LastPausesMs, pauseMs) 146 | stat.LastPausesMcs = append(stat.LastPausesMcs, pauseMcs) 147 | 148 | if stat.prevNumGC = (stat.prevNumGC + 1) % mod; stat.prevNumGC == numGC { 149 | break 150 | } 151 | } 152 | 153 | return 154 | } 155 | 156 | // LogRotate переоткрывает указанный файл и подменяем stdout/stderr вывод на этот файл 157 | func LogRotate(prevLogFd *os.File, fname string) (newLogFd *os.File, err error) { 158 | if prevLogFd != nil { 159 | prevLogFd.Close() 160 | prevLogFd = nil 161 | } 162 | 163 | flag := os.O_CREATE | os.O_APPEND | os.O_WRONLY 164 | newLogFd, err = os.OpenFile(fname, flag, os.FileMode(0644)) 165 | if err != nil { 166 | return nil, err 167 | } 168 | 169 | syscall.Dup2(int(newLogFd.Fd()), syscall.Stdout) 170 | syscall.Dup2(int(newLogFd.Fd()), syscall.Stderr) 171 | 172 | return newLogFd, nil 173 | } 174 | 175 | func SplitNetworkAddr(addr string) (string, uint16, error) { 176 | if host, portStr, err := net.SplitHostPort(addr); err != nil { 177 | return host, 0, err 178 | } else if port, err := net.LookupPort(``, portStr); err != nil { 179 | return host, 0, err 180 | } else { 181 | return host, uint16(port), nil 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /srvfunc/cpu.go: -------------------------------------------------------------------------------- 1 | package srvfunc 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "os/exec" 8 | "strconv" 9 | "strings" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | type ( 15 | // CPUInfo считает статистику по использованию CPU глобально по ОС 16 | CPUInfo struct { 17 | procUsage map[string]float32 18 | procUsageMu sync.Mutex 19 | 20 | clockTicks int // Clock ticks/second 21 | 22 | // нагрузка по CPU: средняя за время время (avg) и средняя за последнее время (cur) 23 | avgCpuUsagePerc int 24 | curCpuUsagePerc int 25 | cpuUsagesPerc [60]int 26 | lastCpuUsagesIdx int 27 | 28 | cpuNum int // кешируем ответ GetCPUNum() 29 | } 30 | ) 31 | 32 | // MakeCPUInfo инициализирует сбор статистики 33 | func MakeCPUInfo() *CPUInfo { 34 | ci := &CPUInfo{} 35 | ci.init() 36 | 37 | return ci 38 | } 39 | 40 | // GetThisProcUsage возвращает текущую статистику использования CPU в целом по системе: us, ni, sy, id, io 41 | // Если от инициализации (MakeCPUInfo) прошло менее секунды, то вернет пустой словарь 42 | func (ci *CPUInfo) GetThisProcUsage() map[string]float32 { 43 | m := make(map[string]float32) 44 | 45 | ci.procUsageMu.Lock() 46 | for k, v := range ci.procUsage { 47 | m[k] = v 48 | } 49 | ci.procUsageMu.Unlock() 50 | 51 | return m 52 | } 53 | 54 | // GetSelfCpuUsage возвращает статистику по использованию CPU текущим процессом: 55 | // среднее и последнее использование в % (100 - полностью занято 1 ядро, 800 - полностью заняты 8 ядер и т.п.) 56 | func (ci *CPUInfo) GetSelfCpuUsage() (avgPerc int, curPerc int) { 57 | return ci.avgCpuUsagePerc, ci.curCpuUsagePerc 58 | } 59 | 60 | // GetCPUNum возвращает число ядер (виртуальных) CPU 61 | func (ci *CPUInfo) GetCPUNum() (int, error) { 62 | if ci.cpuNum == 0 { 63 | if buf, err := exec.Command(`nproc`).Output(); err != nil { 64 | return 0, err 65 | } else if n, err := strconv.Atoi(strings.TrimSpace(string(buf))); err != nil { 66 | return 0, err 67 | } else { 68 | ci.cpuNum = n 69 | } 70 | } 71 | 72 | return ci.cpuNum, nil 73 | } 74 | 75 | func (ci *CPUInfo) init() { 76 | ci.procUsage = make(map[string]float32) 77 | 78 | ci.clockTicks = 0 79 | if out, err := exec.Command(`getconf`, `CLK_TCK`).Output(); err != nil { 80 | } else if n, err := strconv.ParseUint(string(bytes.TrimSpace(out)), 10, 32); err != nil { 81 | } else if n > 0 { 82 | ci.clockTicks = int(n) 83 | } 84 | if ci.clockTicks == 0 { 85 | ci.clockTicks = 100 // стандартный вариант 86 | } 87 | 88 | go ci.allSystemCpuUsageLoop() 89 | go ci.thisProcessCpuUsageLoop() 90 | } 91 | 92 | func (ci *CPUInfo) allSystemCpuUsageLoop() { 93 | bytesNl := []byte("\n") 94 | bytesSpace := []byte(` `) 95 | 96 | titles := []string{`us`, `ni`, `sy`, `id`, `io`} 97 | titlesPref := 2 // сколько ведущих колонок из выдачи /proc/stat пропускаем 98 | 99 | cntsCur := make([]uint64, len(titles)) 100 | cntsPrev := make([]uint64, len(titles)) 101 | var prevTotal uint64 102 | 103 | tick := time.Tick(1 * time.Second) 104 | 105 | for { 106 | if buf, err := ioutil.ReadFile(`/proc/stat`); err != nil { 107 | } else if lines := bytes.SplitN(buf, bytesNl, 2); len(lines) < 2 { 108 | } else if cols := bytes.Split(lines[0], bytesSpace); len(cols) < (len(titles) + titlesPref) { 109 | } else if string(cols[0]) != `cpu` { 110 | } else { 111 | cols = cols[titlesPref:] 112 | 113 | total := uint64(0) 114 | for i := len(titles) - 1; i >= 0; i-- { 115 | if n, err := strconv.Atoi(string(cols[i])); err == nil { 116 | cntsCur[i] = uint64(n) 117 | total += uint64(n) 118 | } 119 | } 120 | 121 | if prevTotal > 0 { 122 | ci.procUsageMu.Lock() 123 | for k := range ci.procUsage { 124 | ci.procUsage[k] = 0 125 | } 126 | 127 | diffTotal := total - prevTotal 128 | for i := range cntsCur { 129 | if diff := cntsCur[i] - cntsPrev[i]; diff > 0 { 130 | ci.procUsage[titles[i]] = 100.0 * float32(diff) / float32(diffTotal) 131 | } 132 | } 133 | ci.procUsageMu.Unlock() 134 | } 135 | 136 | prevTotal = total 137 | for i, n := range cntsCur { 138 | cntsPrev[i] = n 139 | } 140 | } 141 | 142 | <-tick 143 | } 144 | } 145 | 146 | func (ci *CPUInfo) thisProcessCpuUsageLoop() { 147 | readProcSelfStat := func() (totalTime float64, startTime float64, err error) { 148 | raw, err := ioutil.ReadFile(`/proc/self/stat`) 149 | if err != nil { 150 | return 0, 0, err 151 | } 152 | 153 | chunks := bytes.Split(raw, []byte(` `)) 154 | if len(chunks) < 22 { 155 | return 0, 0, ErrSyscallFail 156 | } 157 | 158 | var utime, stime, cutime, cstime, starttime int 159 | 160 | if utime, err = strconv.Atoi(string(chunks[13])); err != nil { 161 | return 0, 0, err 162 | } else if stime, err = strconv.Atoi(string(chunks[14])); err != nil { 163 | return 0, 0, err 164 | } else if cutime, err = strconv.Atoi(string(chunks[15])); err != nil { 165 | return 0, 0, err 166 | } else if cstime, err = strconv.Atoi(string(chunks[16])); err != nil { 167 | return 0, 0, err 168 | } else if starttime, err = strconv.Atoi(string(chunks[21])); err != nil { 169 | return 0, 0, err 170 | } 171 | 172 | totalTime = float64(utime+stime+cutime+cstime) / float64(ci.clockTicks) 173 | startTime = float64(starttime) / float64(ci.clockTicks) 174 | return totalTime, startTime, nil 175 | } 176 | 177 | var ( 178 | startTime float64 179 | prevTotalTime float64 180 | uptime uint64 181 | uptimeTs int64 182 | ) 183 | 184 | ci.lastCpuUsagesIdx = -1 185 | 186 | for { 187 | if uptime == 0 { 188 | // из строки вида "350735.47 234388.90" вычитываю только первое значение до точки 189 | raw, err := ioutil.ReadFile(`/proc/uptime`) 190 | if err != nil { 191 | time.Sleep(50 * time.Millisecond) 192 | continue 193 | } else if n, err := fmt.Sscanf(string(raw), `%d`, &uptime); (err != nil) || (n == 0) { 194 | time.Sleep(50 * time.Millisecond) 195 | continue 196 | } 197 | uptimeTs = time.Now().Unix() 198 | } 199 | 200 | totalTime, _startTime, err := readProcSelfStat() 201 | if err != nil { 202 | time.Sleep(50 * time.Millisecond) 203 | continue 204 | } 205 | startTime = _startTime 206 | 207 | if prevTotalTime > 0 { 208 | currentUptime := float64(uptime) + float64(time.Now().Unix()-uptimeTs) 209 | ci.avgCpuUsagePerc = int(100 * (totalTime / (currentUptime - startTime))) 210 | 211 | // расчет средней текущей нагрузки через циклический буфер точечных замеров 212 | ci.lastCpuUsagesIdx++ 213 | ci.lastCpuUsagesIdx = ci.lastCpuUsagesIdx % len(ci.cpuUsagesPerc) 214 | ci.cpuUsagesPerc[ci.lastCpuUsagesIdx] = int(100 * (totalTime - prevTotalTime)) 215 | 216 | avg := 0 217 | for _, v := range ci.cpuUsagesPerc { 218 | avg += v 219 | } 220 | ci.curCpuUsagePerc = avg / len(ci.cpuUsagesPerc) 221 | } 222 | prevTotalTime = totalTime 223 | 224 | time.Sleep(1 * time.Second) 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /srvfunc/dialer.go: -------------------------------------------------------------------------------- 1 | package srvfunc 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "context" 7 | "io" 8 | "log" 9 | "net" 10 | "os" 11 | "sync" 12 | ) 13 | 14 | var ( 15 | emptyDialer = &net.Dialer{} 16 | 17 | hostsCache = struct { 18 | m map[string]string // hostname => ip 19 | sync.RWMutex 20 | }{ 21 | m: make(map[string]string), 22 | } 23 | ) 24 | 25 | func skipLongLine(rd *bufio.Reader) (eof bool) { 26 | for { 27 | _, isPrefix, err := rd.ReadLine() 28 | if err == io.EOF { 29 | return true 30 | } else if err != nil { 31 | log.Printf("Error while parsing /etc/hosts: %s", err.Error()) 32 | return true 33 | } 34 | 35 | if !isPrefix { 36 | break 37 | } 38 | } 39 | 40 | return false 41 | } 42 | 43 | func etcHostsLookup(hostname string) string { 44 | hostsCache.RLock() 45 | ip, ok := hostsCache.m[hostname] 46 | hostsCache.RUnlock() 47 | if ok { 48 | return ip 49 | } 50 | 51 | hostsCache.Lock() 52 | ip, ok = hostsCache.m[hostname] 53 | if ok { 54 | hostsCache.Unlock() 55 | return ip 56 | } 57 | defer hostsCache.Unlock() 58 | 59 | fp, err := os.Open("/etc/hosts") 60 | if err != nil { 61 | log.Printf("Could not open /etc/hosts: %s", err.Error()) 62 | return "" 63 | } 64 | 65 | defer fp.Close() 66 | 67 | hostnameBytes := []byte(hostname) 68 | 69 | rd := bufio.NewReader(fp) 70 | 71 | for { 72 | ln, isPrefix, err := rd.ReadLine() 73 | 74 | if err == io.EOF { 75 | break 76 | } else if err != nil { 77 | log.Printf("Error while reading /etc/hosts: %s", err.Error()) 78 | return "" 79 | } 80 | 81 | // do not attempt to parse long lines, we should not really have them in /etc/hosts 82 | if isPrefix { 83 | if eof := skipLongLine(rd); eof { 84 | return "" 85 | } 86 | } 87 | 88 | if len(ln) <= 1 || ln[0] == '#' { 89 | continue 90 | } 91 | 92 | if !bytes.Contains(ln, hostnameBytes) { 93 | continue 94 | } 95 | 96 | // 127.0.0.1 localhost loclahost loclhsot lolcahost 97 | fields := bytes.Fields(ln) 98 | 99 | if len(fields) <= 1 { 100 | continue 101 | } 102 | 103 | // ensure that it is IPv4 address because we do not support dual stack in this resolver anyway 104 | if ip := net.ParseIP(string(fields[0])); ip == nil || ip.To4() == nil { 105 | continue 106 | } 107 | 108 | for _, f := range fields[1:] { 109 | if bytes.Equal(hostnameBytes, f) { 110 | return string(fields[0]) 111 | } 112 | } 113 | } 114 | 115 | return "" 116 | } 117 | 118 | // CachingDialer should be used as DialContext function in http.Transport to speed up DNS resolution dramatically. 119 | func CachingDialer(ctx context.Context, network, addr string) (net.Conn, error) { 120 | if network != "tcp" && network != "udp" { 121 | return emptyDialer.DialContext(ctx, network, addr) 122 | } 123 | 124 | host, port, err := net.SplitHostPort(addr) 125 | if err != nil { 126 | return nil, err 127 | } 128 | 129 | // check if it already is an IP address, no need to resolve in this case 130 | if ip := net.ParseIP(host); ip != nil { 131 | return emptyDialer.DialContext(ctx, network, addr) 132 | } 133 | 134 | if hostIP := etcHostsLookup(host); hostIP != "" { 135 | return emptyDialer.DialContext(ctx, network, hostIP+":"+port) 136 | } 137 | 138 | return emptyDialer.DialContext(ctx, network, addr) 139 | } 140 | -------------------------------------------------------------------------------- /srvfunc/errors.go: -------------------------------------------------------------------------------- 1 | package srvfunc 2 | 3 | import ( 4 | goerrors "github.com/go-errors/errors" 5 | "os" 6 | ) 7 | 8 | // Gorecover служит оберткой над recover() и go-errors для симпатичного вывода ошибки или вызова callback в случае ошибки 9 | func Gorecover(cb func(stack string)) { 10 | if err := recover(); err == nil { 11 | } else if stack := goerrors.Wrap(err, 3).ErrorStack(); cb == nil { 12 | os.Stderr.WriteString(stack) 13 | os.Stderr.WriteString("\n") 14 | os.Exit(1) 15 | } else { 16 | cb(stack) 17 | } 18 | } 19 | 20 | // EWrap оборачивает ошибку в *goerrors.Error 21 | // Если ошибка уже обернута, не трогает ее. 22 | // Если передан nil, то nil и возвращает. 23 | func EWrap(err error) error { 24 | if err == nil { 25 | return nil 26 | } 27 | return goerrors.Wrap(err, 1) 28 | } 29 | 30 | // EIs сокращение для goerrors.Is 31 | // Исключительно чтобы не использовать явный импорт goerrors, если данный пакет уже импортирован 32 | func EIs(err error, original error) bool { 33 | return goerrors.Is(err, original) 34 | } 35 | -------------------------------------------------------------------------------- /srvfunc/pprof.go: -------------------------------------------------------------------------------- 1 | package srvfunc 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "runtime/pprof" 7 | ) 8 | 9 | type ( 10 | cpuprofiler struct { 11 | } 12 | 13 | memprofiler struct { 14 | fd io.WriteCloser 15 | } 16 | 17 | gorprofiler struct { 18 | fd io.WriteCloser 19 | } 20 | ) 21 | 22 | // MakeCPUProfile инициализирует запись cpu профиля в файл 23 | // Файл наполняется все время работы приложения 24 | func MakeCPUProfile(path string) (io.Closer, error) { 25 | if f, err := os.Create(path); err != nil { 26 | return nil, err 27 | } else if err := pprof.StartCPUProfile(f); err != nil { 28 | return nil, err 29 | } else { 30 | return &cpuprofiler{}, nil 31 | } 32 | } 33 | 34 | // MakeMemProfile открывает файл для записи mem профиля 35 | // Реальная запись в файл происходит при закрытии профилировщика 36 | func MakeMemProfile(path string) (io.Closer, error) { 37 | f, err := os.Create(path) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return &memprofiler{fd: f}, nil 42 | } 43 | 44 | // MakeGorProfile открывает файл для записи статистике по горутинам 45 | // Реальная запись в файл происходит при закрытии профилировщика 46 | func MakeGorProfile(path string) (io.Closer, error) { 47 | f, err := os.Create(path) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return &gorprofiler{fd: f}, nil 52 | } 53 | 54 | func (cp *cpuprofiler) Close() error { 55 | pprof.StopCPUProfile() 56 | return nil 57 | } 58 | 59 | func (mp *memprofiler) Close() error { 60 | defer mp.fd.Close() 61 | return pprof.WriteHeapProfile(mp.fd) 62 | } 63 | 64 | func (gp *gorprofiler) Close() error { 65 | defer gp.fd.Close() 66 | return pprof.Lookup(`goroutine`).WriteTo(gp.fd, 1) 67 | } 68 | -------------------------------------------------------------------------------- /srvfunc/user.go: -------------------------------------------------------------------------------- 1 | package srvfunc 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "io" 7 | "os" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | var ( 13 | ErrNotFound = errors.New(`Not found`) 14 | ErrSyscallFail = errors.New(`Syscall fail`) 15 | ) 16 | 17 | func parseEtcPasswdGroup(fname string) (map[string][]string, error) { 18 | fd, err := os.Open(fname) 19 | if err != nil { 20 | return nil, EWrap(err) 21 | } 22 | defer fd.Close() 23 | 24 | rd := bufio.NewReader(fd) 25 | m := make(map[string][]string) 26 | 27 | for { 28 | line, err := rd.ReadString('\n') 29 | if err != nil { 30 | if err == io.EOF { 31 | break 32 | } 33 | return nil, EWrap(err) 34 | } 35 | 36 | chunks := strings.Split(line, `:`) 37 | if len(chunks) > 1 { 38 | m[chunks[0]] = chunks[1:] 39 | } 40 | } 41 | 42 | return m, nil 43 | } 44 | 45 | // LookupUidByName ищет сведения по пользователю в /etc/passwd 46 | func LookupUidByName(name string) (int, error) { 47 | if m, err := parseEtcPasswdGroup(`/etc/passwd`); err != nil { 48 | return -1, err 49 | } else if user, ok := m[name]; !ok { 50 | return -1, EWrap(ErrNotFound) 51 | } else if n, err := strconv.ParseUint(user[1], 10, 32); err != nil { 52 | return -1, err 53 | } else { 54 | return int(n), nil 55 | } 56 | } 57 | 58 | // LookupGidByName ищет сведения по группе с /etc/group 59 | func LookupGidByName(name string) (int, error) { 60 | if m, err := parseEtcPasswdGroup(`/etc/group`); err != nil { 61 | return -1, err 62 | } else if group, ok := m[name]; !ok { 63 | return -1, EWrap(ErrNotFound) 64 | } else if n, err := strconv.ParseUint(group[1], 10, 32); err != nil { 65 | return -1, err 66 | } else { 67 | return int(n), nil 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /srvfunc/user_darwin.go: -------------------------------------------------------------------------------- 1 | package srvfunc 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | 8 | // ChangeUser установка ID пользователя для текущего потока 9 | func ChangeUser(user string) error { 10 | if uid, err := LookupUidByName(user); err != nil { 11 | return err 12 | } else if status := C.setuid(C.uid_t(uid)); status != 0 { 13 | return EWrap(ErrSyscallFail) 14 | } 15 | 16 | return nil 17 | } 18 | 19 | // ChangeGroup устанавливает ID группы для текущего потока 20 | func ChangeGroup(group string) error { 21 | if gid, err := LookupGidByName(group); err != nil { 22 | return err 23 | } else if status := C.setgid(C.gid_t(gid)); status != 0 { 24 | return EWrap(ErrSyscallFail) 25 | } 26 | 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /srvfunc/user_linux.go: -------------------------------------------------------------------------------- 1 | package srvfunc 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | import "os" 8 | 9 | // ChangeUser установка ID пользователя для текущего потока 10 | func ChangeUser(user string) error { 11 | if uid, err := LookupUidByName(user); err != nil { 12 | return err 13 | } else if status := C.setuid(C.__uid_t(uid)); status != 0 { 14 | return EWrap(ErrSyscallFail) 15 | } 16 | 17 | // чтобы последующий код, например RPCConn.Connect, видел правильное имя пользователя 18 | os.Setenv(`USER`, user) 19 | 20 | return nil 21 | } 22 | 23 | // ChangeGroup устанавливает ID группы для текущего потока 24 | func ChangeGroup(group string) error { 25 | if gid, err := LookupGidByName(group); err != nil { 26 | return err 27 | } else if status := C.setgid(C.__gid_t(gid)); status != 0 { 28 | return EWrap(ErrSyscallFail) 29 | } 30 | 31 | return nil 32 | } 33 | --------------------------------------------------------------------------------