将 lal/pkg/util 的代码拷贝至本仓库中. 修改代码中import lal的路径为本仓库路径

pull/2/head
q191201771 6 years ago
parent 849f0a8191
commit bee8b4cbbe

3
.gitignore vendored

@ -0,0 +1,3 @@
coverage.txt
coverage.html
profile.out

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/nezha.iml" filepath="$PROJECT_DIR$/.idea/nezha.iml" />
</modules>
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -0,0 +1,229 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="9bce1ada-5cee-4df2-b9a4-a939ba12b38a" name="Default Changelist" comment="" />
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileEditorManager">
<leaf>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/assert/assert.go">
<provider selected="true" editor-type-id="text-editor" />
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/bele/bele_test.go">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="95">
<caret line="5" column="31" selection-start-line="5" selection-start-column="31" selection-end-line="5" selection-end-column="31" />
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/connstat2/connstat2_test.go">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="57">
<caret line="3" column="31" selection-start-line="3" selection-start-column="31" selection-end-line="3" selection-end-column="31" />
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/log/log_test.go">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="73">
<caret line="4" column="31" selection-start-line="4" selection-start-column="31" selection-end-line="4" selection-end-column="31" />
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/unique/unique_test.go">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="55">
<caret line="3" column="32" selection-start-line="3" selection-start-column="32" selection-end-line="3" selection-end-column="32" />
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/bele/bele.go">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="171">
<caret line="9" selection-start-line="9" selection-end-line="9" />
<folding>
<element signature="e#126#169#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="GOROOT" path="/usr/local/go" />
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="IdeDocumentHistory">
<option name="CHANGED_PATHS">
<list>
<option value="$PROJECT_DIR$/bele/bele_test.go" />
<option value="$PROJECT_DIR$/connstat2/connstat2_test.go" />
<option value="$PROJECT_DIR$/log/log_test.go" />
<option value="$PROJECT_DIR$/unique/unique_test.go" />
</list>
</option>
</component>
<component name="ProjectConfigurationFiles">
<option name="files">
<list>
<option value="$PROJECT_DIR$/.idea/nezha.iml" />
<option value="$PROJECT_DIR$/.idea/misc.xml" />
<option value="$PROJECT_DIR$/.idea/modules.xml" />
<option value="$PROJECT_DIR$/.idea/vcs.xml" />
</list>
</option>
</component>
<component name="ProjectFrameBounds">
<option name="x" value="4" />
<option name="y" value="23" />
<option name="width" value="1276" />
<option name="height" value="777" />
</component>
<component name="ProjectView">
<navigator proportions="" version="1">
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="Scope" />
<pane id="ProjectPane">
<subPane>
<expand>
<path>
<item name="nezha" type="b2602c69:ProjectViewProjectNode" />
<item name="nezha" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="nezha" type="b2602c69:ProjectViewProjectNode" />
<item name="nezha" type="462c0819:PsiDirectoryNode" />
<item name="assert" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="nezha" type="b2602c69:ProjectViewProjectNode" />
<item name="nezha" type="462c0819:PsiDirectoryNode" />
<item name="bele" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="nezha" type="b2602c69:ProjectViewProjectNode" />
<item name="nezha" type="462c0819:PsiDirectoryNode" />
<item name="log" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="nezha" type="b2602c69:ProjectViewProjectNode" />
<item name="nezha" type="462c0819:PsiDirectoryNode" />
<item name="unique" type="462c0819:PsiDirectoryNode" />
</path>
</expand>
<select />
</subPane>
</pane>
</panes>
</component>
<component name="PropertiesComponent">
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="go.gopath.indexing.explicitly.defined" value="true" />
<property name="go.import.settings.migrated" value="true" />
<property name="go.sdk.automatically.set" value="true" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
<property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
<property name="nodejs_npm_path_reset_for_default_project" value="true" />
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="ToolWindowManager">
<frame x="4" y="23" width="1276" height="777" extended-state="6" />
<editor active="true" />
<layout>
<window_info id="Favorites" side_tool="true" />
<window_info active="true" content_ui="combo" id="Project" order="0" visible="true" weight="0.24958813" />
<window_info id="Structure" order="1" side_tool="true" weight="0.25" />
<window_info anchor="bottom" id="Docker" show_stripe_button="false" />
<window_info anchor="bottom" id="Database Changes" />
<window_info anchor="bottom" id="Version Control" />
<window_info anchor="bottom" id="Terminal" />
<window_info anchor="bottom" id="Event Log" side_tool="true" />
<window_info anchor="bottom" id="Message" order="0" />
<window_info anchor="bottom" id="Find" order="1" />
<window_info anchor="bottom" id="Run" order="2" />
<window_info anchor="bottom" id="Debug" order="3" weight="0.4" />
<window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
<window_info anchor="bottom" id="TODO" order="6" />
<window_info anchor="right" id="Database" />
<window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
<window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
</layout>
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" />
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/assert/assert.go">
<provider selected="true" editor-type-id="text-editor" />
</entry>
<entry file="file://$PROJECT_DIR$/bele/bele.go">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="171">
<caret line="9" selection-start-line="9" selection-end-line="9" />
<folding>
<element signature="e#126#169#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/bele/bele_test.go">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="95">
<caret line="5" column="31" selection-start-line="5" selection-start-column="31" selection-end-line="5" selection-end-column="31" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/connstat2/connstat2_test.go">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="57">
<caret line="3" column="31" selection-start-line="3" selection-start-column="31" selection-end-line="3" selection-end-column="31" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/log/log_test.go">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="73">
<caret line="4" column="31" selection-start-line="4" selection-start-column="31" selection-end-line="4" selection-end-column="31" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/unique/unique_test.go">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="55">
<caret line="3" column="32" selection-start-line="3" selection-start-column="32" selection-end-line="3" selection-end-column="32" />
</state>
</provider>
</entry>
</component>
</project>

@ -0,0 +1,53 @@
// Package assert 提供了单元测试时的断言功能
//
// 代码参考了 https://github.com/stretchr/testify
//
package assert
import (
"bytes"
"reflect"
)
type TestingT interface {
Errorf(format string, args ...interface{})
}
func Equal(t TestingT, expected interface{}, actual interface{}, msg ...string) {
if !equal(expected, actual) {
t.Errorf("%s expected=%+v, actual=%+v", msg, expected, actual)
}
return
}
func isNil(actual interface{}) bool {
if actual == nil {
return true
}
v := reflect.ValueOf(actual)
k := v.Kind()
if k == reflect.Chan || k == reflect.Map || k == reflect.Ptr || k == reflect.Interface || k == reflect.Slice {
return v.IsNil()
}
return false
}
func equal(expected, actual interface{}) bool {
if expected == nil {
return isNil(actual)
}
exp, ok := expected.([]byte)
if !ok {
return reflect.DeepEqual(expected, actual)
}
act, ok := actual.([]byte)
if !ok {
return false
}
//if exp == nil || act == nil {
// return exp == nil && act == nil
//}
return bytes.Equal(exp, act)
}

@ -0,0 +1,26 @@
package assert
import "testing"
func TestEqual(t *testing.T) {
Equal(t, nil, nil)
Equal(t, nil, nil, "fxxk.")
Equal(t, 1, 1)
Equal(t, "aaa", "aaa")
var ch chan struct{}
Equal(t, nil, ch)
var m map[string]string
Equal(t, nil, m)
var p *int
Equal(t, nil, p)
var i interface{}
Equal(t, nil, i)
var b []byte
Equal(t, nil, b)
Equal(t, true, isNil(nil))
Equal(t, false, isNil("aaa"))
Equal(t, false, equal([]byte{}, "aaa"))
Equal(t, true, equal([]byte{}, []byte{}))
Equal(t, true, equal([]byte{0, 1, 2}, []byte{0, 1, 2}))
}

@ -0,0 +1,66 @@
// Package bele 提供了大小端的转换操作
//
// be是big endian的缩写即大端
// le是little endian的缩写即小端
//
// assume local is `le`
//
package bele
import (
"encoding/binary"
"io"
"math"
)
func BEUint16(p []byte) uint16 {
return binary.BigEndian.Uint16(p)
}
func BEUint24(p []byte) (ret uint32) {
ret = 0
ret |= uint32(p[0]) << 16
ret |= uint32(p[1]) << 8
ret |= uint32(p[2])
return
}
func BEUint32(p []byte) (ret uint32) {
return binary.BigEndian.Uint32(p)
}
func BEFloat64(p []byte) (ret float64) {
a := binary.BigEndian.Uint64(p)
return math.Float64frombits(a)
}
func LEUint32(p []byte) (ret uint32) {
return binary.LittleEndian.Uint32(p)
}
func BEPutUint24(out []byte, in uint32) {
out[0] = byte(in >> 16)
out[1] = byte(in >> 8)
out[2] = byte(in)
}
func BEPutUint32(out []byte, in uint32) {
binary.BigEndian.PutUint32(out, in)
}
func LEPutUint32(out []byte, in uint32) {
binary.LittleEndian.PutUint32(out, in)
}
func WriteBEUint24(writer io.Writer, in uint32) error {
_, err := writer.Write([]byte{uint8(in >> 16), uint8(in >> 8), uint8(in & 0xFF)})
return err
}
func WriteBE(writer io.Writer, in interface{}) error {
return binary.Write(writer, binary.BigEndian, in)
}
func WriteLE(writer io.Writer, in interface{}) error {
return binary.Write(writer, binary.LittleEndian, in)
}

@ -0,0 +1,203 @@
package bele
import (
"bytes"
"encoding/binary"
"github.com/q191201771/nezha/assert"
"testing"
)
func TestBEUint16(t *testing.T) {
vector := []struct {
input []byte
output uint16
}{
{input: []byte{0, 0}, output: 0},
{input: []byte{0, 1}, output: 1},
{input: []byte{0, 255}, output: 255},
{input: []byte{1, 0}, output: 256},
{input: []byte{255, 0}, output: 255 * 256},
{input: []byte{12, 34}, output: 12*256 + 34},
}
for i := 0; i < len(vector); i++ {
assert.Equal(t, vector[i].output, BEUint16(vector[i].input))
}
}
func TestBEUint24(t *testing.T) {
vector := []struct {
input []byte
output uint32
}{
{input: []byte{0, 0, 0}, output: 0},
{input: []byte{0, 0, 1}, output: 1},
{input: []byte{0, 1, 0}, output: 256},
{input: []byte{1, 0, 0}, output: 1 * 256 * 256},
{input: []byte{12, 34, 56}, output: 12*256*256 + 34*256 + 56},
}
for i := 0; i < len(vector); i++ {
assert.Equal(t, vector[i].output, BEUint24(vector[i].input))
}
}
func TestBEUint32(t *testing.T) {
vector := []struct {
input []byte
output uint32
}{
{input: []byte{0, 0, 0, 0}, output: 0},
{input: []byte{0, 0, 1, 0}, output: 1 * 256},
{input: []byte{0, 1, 0, 0}, output: 1 * 256 * 256},
{input: []byte{1, 0, 0, 0}, output: 1 * 256 * 256 * 256},
{input: []byte{12, 34, 56, 78}, output: 12*256*256*256 + 34*256*256 + 56*256 + 78},
}
for i := 0; i < len(vector); i++ {
assert.Equal(t, vector[i].output, BEUint32(vector[i].input))
}
}
func TestBEFloat64(t *testing.T) {
vector := []int{
1,
0xFF,
0xFFFF,
0xFFFFFF,
}
for i := 0; i < len(vector); i++ {
b := &bytes.Buffer{}
err := binary.Write(b, binary.BigEndian, float64(vector[i]))
assert.Equal(t, nil, err)
assert.Equal(t, vector[i], int(BEFloat64(b.Bytes())))
}
}
func TestLEUint32(t *testing.T) {
vector := []struct {
input []byte
output uint32
}{
{input: []byte{0, 0, 0, 0}, output: 0},
{input: []byte{0, 0, 1, 0}, output: 1 * 256 * 256},
{input: []byte{0, 1, 0, 0}, output: 1 * 256},
{input: []byte{1, 0, 0, 0}, output: 1},
{input: []byte{12, 34, 56, 78}, output: 12 + 34*256 + 56*256*256 + 78*256*256*256},
}
for i := 0; i < len(vector); i++ {
assert.Equal(t, vector[i].output, LEUint32(vector[i].input))
}
}
func TestBEPutUint24(t *testing.T) {
vector := []struct {
input uint32
output []byte
}{
{input: 0, output: []byte{0, 0, 0}},
{input: 1, output: []byte{0, 0, 1}},
{input: 256, output: []byte{0, 1, 0}},
{input: 1 * 256 * 256, output: []byte{1, 0, 0}},
{input: 12*256*256 + 34*256 + 56, output: []byte{12, 34, 56}},
}
out := make([]byte, 3)
for i := 0; i < len(vector); i++ {
BEPutUint24(out, vector[i].input)
assert.Equal(t, vector[i].output, out)
}
}
func TestBEPutUint32(t *testing.T) {
vector := []struct {
input uint32
output []byte
}{
{input: 0, output: []byte{0, 0, 0, 0}},
{input: 1 * 256, output: []byte{0, 0, 1, 0}},
{input: 1 * 256 * 256, output: []byte{0, 1, 0, 0}},
{input: 1 * 256 * 256 * 256, output: []byte{1, 0, 0, 0}},
{input: 12*256*256*256 + 34*256*256 + 56*256 + 78, output: []byte{12, 34, 56, 78}},
}
out := make([]byte, 4)
for i := 0; i < len(vector); i++ {
BEPutUint32(out, vector[i].input)
assert.Equal(t, vector[i].output, out)
}
}
func TestLEPutUint32(t *testing.T) {
vector := []struct {
input uint32
output []byte
}{
{input: 0, output: []byte{0, 0, 0, 0}},
{input: 1 * 256 * 256, output: []byte{0, 0, 1, 0}},
{input: 1 * 256, output: []byte{0, 1, 0, 0}},
{input: 1, output: []byte{1, 0, 0, 0}},
{input: 78*256*256*256 + 56*256*256 + 34*256 + 12, output: []byte{12, 34, 56, 78}},
}
out := make([]byte, 4)
for i := 0; i < len(vector); i++ {
LEPutUint32(out, vector[i].input)
assert.Equal(t, vector[i].output, out)
}
}
func TestWriteBEUint24(t *testing.T) {
vector := []struct {
input uint32
output []byte
}{
{input: 0, output: []byte{0, 0, 0}},
{input: 1, output: []byte{0, 0, 1}},
{input: 256, output: []byte{0, 1, 0}},
{input: 1 * 256 * 256, output: []byte{1, 0, 0}},
{input: 12*256*256 + 34*256 + 56, output: []byte{12, 34, 56}},
}
for i := 0; i < len(vector); i++ {
out := &bytes.Buffer{}
err := WriteBEUint24(out, vector[i].input)
assert.Equal(t, nil, err)
assert.Equal(t, vector[i].output, out.Bytes())
}
}
func TestWriteBE(t *testing.T) {
vector := []struct {
input interface{}
output []byte
}{
{input: uint32(1), output: []byte{0, 0, 0, 1}},
{input: uint64(1), output: []byte{0, 0, 0, 0, 0, 0, 0, 1}},
}
for i := 0; i < len(vector); i++ {
out := &bytes.Buffer{}
err := WriteBE(out, vector[i].input)
assert.Equal(t, nil, err)
assert.Equal(t, vector[i].output, out.Bytes())
}
}
func TestWriteLE(t *testing.T) {
vector := []struct {
input interface{}
output []byte
}{
{input: uint32(1), output: []byte{1, 0, 0, 0}},
{input: uint64(1), output: []byte{1, 0, 0, 0, 0, 0, 0, 0}},
}
for i := 0; i < len(vector); i++ {
out := &bytes.Buffer{}
err := WriteLE(out, vector[i].input)
assert.Equal(t, nil, err)
assert.Equal(t, vector[i].output, out.Bytes())
}
}
// TODO chef: benchmark

@ -0,0 +1,41 @@
package bininfo
import (
"fmt"
"runtime"
)
// 编译时通过如下方式传入编译时信息
// go build -ldflags " \
// -X 'github.com/q191201771/lal/pkg/util/bininfo.GitCommitID=`git log --pretty=format:'%h' -n 1`' \
// -X 'github.com/q191201771/lal/pkg/util/bininfo.BuildTime=`date +'%Y.%m.%d.%H%M%S'`' \
// -X 'github.com/q191201771/lal/pkg/util/bininfo.BuildGoVersion=`go version`' \
// "
var (
GitCommitID string
BuildTime string
BuildGoVersion string
)
func StringifySingleLine() string {
return fmt.Sprintf("GitCommitID=%s. BuildTime=%s. GoVersion=%s. runtime=%s/%s.",
GitCommitID, BuildTime, BuildGoVersion, runtime.GOOS, runtime.GOARCH)
}
func StringifyMultiLine() string {
return fmt.Sprintf("GitCommitID=%s\nBuildTime=%s\nGoVersion=%s\nruntime=%s/%s.",
GitCommitID, BuildTime, BuildGoVersion, runtime.GOOS, runtime.GOARCH)
}
func init() {
if GitCommitID == "" {
GitCommitID = "unknown"
}
if BuildTime == "" {
BuildTime = "unknown"
}
if BuildGoVersion == "" {
BuildGoVersion = "unknown"
}
}

@ -0,0 +1,82 @@
package connstat
import (
"sync/atomic"
"time"
)
// 高性能场景下实现长连接流数据读写超时功能
// 不使用Go的库函数SetDeadline
// 也不在每次读写数据时使用time now获取读写时间
// 允许出现两秒左右的误差
type ConnStat struct {
readTimeout int64
writeTimeout int64
totalReadByte uint64
totalWriteByte uint64
prevTotalReadByte uint64
prevTotalWriteByte uint64
lastReadActiveTick int64
lastWriteActiveTick int64
}
// 单位秒设置为0则用于不超时
func (cs *ConnStat) Start(readTimeout int64, writeTimeout int64) {
cs.readTimeout = readTimeout
cs.writeTimeout = writeTimeout
now := time.Now().Unix()
cs.lastReadActiveTick = now
cs.lastWriteActiveTick = now
}
func (cs *ConnStat) Read(n int) {
atomic.AddUint64(&cs.totalReadByte, uint64(n))
}
func (cs *ConnStat) Write(n int) {
atomic.AddUint64(&cs.totalWriteByte, uint64(n))
}
// 检查时传入当前时间戳。检查频率应该小于超时阈值。频率越低,则越精确
func (cs *ConnStat) Check(now int64) (isReadTimeout bool, isWriteTimeout bool) {
if cs.readTimeout == 0 { // 没有设置,则不用检查
isReadTimeout = false
} else {
trb := atomic.LoadUint64(&cs.totalReadByte)
if trb == 0 { // 历史从来没有收到过数据
isReadTimeout = (now - cs.lastReadActiveTick) > cs.readTimeout
} else {
if trb-cs.prevTotalReadByte > 0 { // 距离上次检查有收到过数据
isReadTimeout = false
cs.lastReadActiveTick = now
} else {
isReadTimeout = (now - cs.lastReadActiveTick) > cs.readTimeout
}
}
cs.prevTotalReadByte = trb
}
if cs.writeTimeout == 0 {
isWriteTimeout = false
} else {
twb := atomic.LoadUint64(&cs.totalWriteByte)
if twb == 0 {
isWriteTimeout = (now - cs.lastWriteActiveTick) > cs.writeTimeout
} else {
if twb-cs.prevTotalWriteByte > 0 {
isWriteTimeout = false
cs.lastWriteActiveTick = now
} else {
isWriteTimeout = (now - cs.lastWriteActiveTick) > cs.writeTimeout
}
}
cs.prevTotalWriteByte = twb
}
return
}

@ -0,0 +1 @@
package connstat2

@ -0,0 +1,37 @@
package connstat2
import (
"github.com/q191201771/nezha/assert"
"net"
"testing"
)
func startMockServer(t assert.TestingT) {
l, err := net.Listen("tcp", ":10027")
assert.Equal(t, nil , err)
go func() {
//for {
conn, err := l.Accept()
assert.Equal(t, nil , err)
go func() {
buf := make([]byte, 8)
for {
_, err = conn.Read(buf)
assert.Equal(t, nil , err)
}
}()
//}
}()
}
func Benchmark(b *testing.B) {
startMockServer(b)
//time.Sleep(time.Duration(1) * time.Second)
conn, err := net.Dial("tcp", ":10027")
assert.Equal(b, nil , err)
buf := make([]byte, 8)
for i := 0; i < b.N; i++ {
conn.Write(buf)
}
}

@ -0,0 +1,7 @@
package errors
func PanicIfErrorOccur(err error) {
if err != nil {
panic(err)
}
}

@ -0,0 +1,274 @@
package log
import (
"errors"
"fmt"
"log"
"os"
"path/filepath"
"sync"
"time"
)
// TODO chef:
// - 性能优化目前是基于系统库log实现的
// - 和系统库中的log跑个benchmark对比
var logErr = errors.New("log:fxxk")
type Logger interface {
Debugf(format string, v ...interface{})
Infof(format string, v ...interface{})
Warnf(format string, v ...interface{})
Errorf(format string, v ...interface{})
Debug(v ...interface{})
Info(v ...interface{})
Warn(v ...interface{})
Error(v ...interface{})
Outputf(level Level, calldepth int, format string, v ...interface{})
Output(level Level, calldepth int, v ...interface{})
}
type Level uint8
const (
LevelDebug = iota
LevelInfo
LevelWarn
LevelError
)
type Config struct {
Level Level `json:"level"` // 日志级别,大于等于该级别的日志才会被输出
Filename string `json:"filename"` // 输出日志文件名,如果为空,则不写日志文件。可包含路径,路径不存在时,将自动创建
IsToStdout bool `json:"is_to_stdout"` // 是否以stdout输出到控制台
// 文件输出和控制台输出可同时打开控制台输出主要用做开发时调试支持level彩色输出以及源码文件、行号
RotateMByte int `json:"rotate_mbyte"` // 日志大小达到多少兆后翻滚如果为0则不翻滚
}
func New(c Config) (Logger, error) {
var (
fl *log.Logger
sl *log.Logger
dir string
fp *os.File
err error
)
if c.Level < LevelDebug || c.Level > LevelError {
return nil, logErr
}
if c.Filename != "" {
dir = filepath.Dir(c.Filename)
if err := os.MkdirAll(dir, 0777); err != nil {
return nil, err
}
fp, err = os.Create(c.Filename)
if err != nil {
return nil, err
}
fl = log.New(fp, "", log.Ldate|log.Lmicroseconds)
}
if c.IsToStdout {
sl = log.New(os.Stdout, "", log.Ldate|log.Lmicroseconds|log.Lshortfile)
}
l := &logger{
fileLogger: fl,
stdoutLogger: sl,
c: c,
dir: dir,
fp: fp,
}
return l, nil
}
const (
levelDebugString = "DEBUG "
levelInfoString = "INFO "
levelWarnString = "WARN "
levelErrorString = "ERROR "
levelDebugColorString = "\033[22;37mDEBUG\033[0m "
levelInfoColorString = "\033[22;36mINFO\033[0m "
levelWarnColorString = "\033[22;33mWARN\033[0m "
levelErrorColorString = "\033[22;31mERROR\033[0m "
)
var (
levelToString = map[Level]string{
LevelDebug: levelDebugString,
LevelInfo: levelInfoString,
LevelWarn: levelWarnString,
LevelError: levelErrorString,
}
levelToColorString = map[Level]string{
LevelDebug: levelDebugColorString,
LevelInfo: levelInfoColorString,
LevelWarn: levelWarnColorString,
LevelError: levelErrorColorString,
}
)
type logger struct {
fileLogger *log.Logger
stdoutLogger *log.Logger
c Config
dir string
m sync.Mutex
fp *os.File
}
func (l *logger) Debugf(format string, v ...interface{}) {
l.Outputf(LevelDebug, 3, format, v...)
}
func (l *logger) Infof(format string, v ...interface{}) {
l.Outputf(LevelInfo, 3, format, v...)
}
func (l *logger) Warnf(format string, v ...interface{}) {
l.Outputf(LevelWarn, 3, format, v...)
}
func (l *logger) Errorf(format string, v ...interface{}) {
l.Outputf(LevelError, 3, format, v...)
}
func (l *logger) Debug(v ...interface{}) {
l.Output(LevelDebug, 3, v...)
}
func (l *logger) Info(v ...interface{}) {
l.Output(LevelInfo, 3, v...)
}
func (l *logger) Warn(v ...interface{}) {
l.Output(LevelWarn, 3, v...)
}
func (l *logger) Error(v ...interface{}) {
l.Output(LevelError, 3, v...)
}
// TODO chef: Outputf 和 Output 代码重复
func (l *logger) Outputf(level Level, calldepth int, format string, v ...interface{}) {
if l.c.Level > level {
return
}
msg := fmt.Sprintf(format, v...)
if l.stdoutLogger != nil {
l.stdoutLogger.Output(calldepth, levelToColorString[level]+msg)
}
if l.fileLogger != nil {
if l.c.RotateMByte > 0 {
l.m.Lock()
// 把写日志的操作也锁住,避免日志移走后,其他协程继续写老日志文件
// TODO chef: 性能比较差,系统库内部也有锁
defer l.m.Unlock()
if fi, err := os.Stat(l.c.Filename); err == nil {
if fi.Size() > int64(l.c.RotateMByte)*1024*1024 {
newFileName := l.c.Filename + "." + time.Now().Format("20060102150405")
if err := os.Rename(l.c.Filename, newFileName); err == nil {
l.fp.Close()
l.fp, _ = os.Create(l.c.Filename)
l.fileLogger.SetOutput(l.fp)
}
}
}
}
l.fileLogger.Output(calldepth, levelToString[level]+msg)
}
}
func (l *logger) Output(level Level, calldepth int, v ...interface{}) {
if l.c.Level > level {
return
}
msg := fmt.Sprint(v...)
if l.stdoutLogger != nil {
l.stdoutLogger.Output(calldepth, levelToColorString[level]+msg)
}
if l.fileLogger != nil {
if l.c.RotateMByte > 0 {
l.m.Lock()
// 把写日志的操作也锁住,避免日志移走后,其他协程继续写老日志文件
// TODO chef: 性能比较差,系统库内部也有锁
defer l.m.Unlock()
if fi, err := os.Stat(l.c.Filename); err == nil {
if fi.Size() > int64(l.c.RotateMByte)*1024*1024 {
newFileName := l.c.Filename + "." + time.Now().Format("20060102150405")
if err := os.Rename(l.c.Filename, newFileName); err == nil {
l.fp.Close()
l.fp, _ = os.Create(l.c.Filename)
l.fileLogger.SetOutput(l.fp)
}
}
}
}
l.fileLogger.Output(calldepth, levelToString[level]+msg)
}
}
var global Logger
func Debugf(format string, v ...interface{}) {
global.Outputf(LevelDebug, 3, format, v...)
}
func Infof(format string, v ...interface{}) {
global.Outputf(LevelInfo, 3, format, v...)
}
func Warnf(format string, v ...interface{}) {
global.Outputf(LevelWarn, 3, format, v...)
}
func Errorf(format string, v ...interface{}) {
global.Outputf(LevelError, 3, format, v...)
}
func Debug(v ...interface{}) {
global.Output(LevelDebug, 3, v...)
}
func Info(v ...interface{}) {
global.Output(LevelInfo, 3, v...)
}
func Warn(v ...interface{}) {
global.Output(LevelWarn, 3, v...)
}
func Error(v ...interface{}) {
global.Output(LevelError, 3, v...)
}
func Outputf(level Level, calldepth int, format string, v ...interface{}) {
global.Outputf(level, calldepth, format, v...)
}
func Output(level Level, calldepth int, v ...interface{}) {
global.Output(level, calldepth, v...)
}
// 这里不加锁保护如果要调用Init函数初始化全局的Logger那么由调用方保证调用Init函数时不会并发调用全局Logger的其他方法
func Init(c Config) error {
var err error
global, err = New(c)
return err
}
func init() {
global, _ = New(Config{
Level: LevelDebug,
IsToStdout: true,
})
}

@ -0,0 +1,93 @@
package log
import (
"testing"
"github.com/q191201771/nezha/assert"
)
func TestLogger(t *testing.T) {
c := Config{
Level: LevelInfo,
Filename: "/tmp/lallogtest/aaa.log",
IsToStdout: true,
RotateMByte: 10,
}
l, err := New(c)
assert.Equal(t, nil, err)
l.Debugf("test msg by Debug%s", "f")
l.Infof("test msg by Info%s", "f")
l.Warnf("test msg by Warn%s", "f")
l.Errorf("test msg by Error%s", "f")
l.Debug("test msg by Debug")
l.Info("test msg by Info")
l.Warn("test msg by Warn")
l.Error("test msg by Error")
}
func TestGlobal(t *testing.T) {
Debugf("test msg by Debug%s", "f")
Infof("test msg by Info%s", "f")
Warnf("test msg by Warn%s", "f")
Errorf("test msg by Error%s", "f")
Debug("test msg by Debug")
Info("test msg by Info")
Warn("test msg by Warn")
Error("test msg by Error")
c := Config{
Level: LevelInfo,
Filename: "/tmp/lallogtest/bbb.log",
IsToStdout: true,
RotateMByte: 10,
}
err := Init(c)
assert.Equal(t, nil, err)
Debugf("test msg by Debug%s", "f")
Infof("test msg by Info%s", "f")
Warnf("test msg by Warn%s", "f")
Errorf("test msg by Error%s", "f")
Debug("test msg by Debug")
Info("test msg by Info")
Warn("test msg by Warn")
Error("test msg by Error")
Output(LevelInfo, 3, "test msg by Output")
Outputf(LevelInfo, 3, "test msg by Output%s", "f")
}
func TestNew(t *testing.T) {
l, err := New(Config{Level:LevelError+1})
assert.Equal(t, nil, l)
assert.Equal(t, logErr, err)
}
func TestRotate(t *testing.T) {
c := Config{
Level: LevelInfo,
Filename: "/tmp/lallogtest/ccc.log",
IsToStdout: false,
RotateMByte: 1,
}
err := Init(c)
assert.Equal(t, nil, err)
b := make([]byte, 1024)
for i := 0; i < 2 * 1024; i++ {
Info(b)
}
for i := 0; i < 2 * 1024; i++ {
Infof("%+v", b)
}
}
func BenchmarkStdout(b *testing.B) {
c := Config{
Level: LevelInfo,
Filename: "/tmp/lallogtest/ccc.log",
IsToStdout: true,
RotateMByte: 10,
}
err := Init(c)
assert.Equal(b, nil, err)
for i := 0; i < b.N; i++ {
Infof("hello %s %d", "world", i)
}
}

@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -e
echo "" > coverage.txt
for d in $(go list ./... | grep -v vendor); do
go test -race -coverprofile=profile.out -covermode=atomic $d
if [ -f profile.out ]; then
cat profile.out >> coverage.txt
rm profile.out
fi
done
# go test -race -coverprofile=profile.out -covermode=atomic && go tool cover -html=profile.out -o coverage.html && open coverage.html
# go test -test.bench=".*"

@ -0,0 +1,16 @@
package unique
import (
"fmt"
"sync/atomic"
)
var globalID = uint64(0)
func GenUniqueKey(prefix string) string {
return fmt.Sprintf("%s%d", prefix, genUniqueID())
}
func genUniqueID() uint64 {
return atomic.AddUint64(&globalID, 1)
}

@ -0,0 +1,25 @@
package unique
import (
"github.com/q191201771/nezha/assert"
"sync"
"testing"
)
func TestGenUniqueKey(t *testing.T) {
m := make(map[string]struct{})
var mutex sync.Mutex
var wg sync.WaitGroup
wg.Add(1000)
for i := 0; i < 1000; i++ {
go func() {
uk := GenUniqueKey("test")
mutex.Lock()
m[uk] = struct{}{}
mutex.Unlock()
wg.Done()
}()
}
wg.Wait()
assert.Equal(t, 1000, len(m))
}
Loading…
Cancel
Save