diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 343f3a7..1ad04ae 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -40,7 +40,7 @@ }, { "ImportPath": "github.com/siddontang/goleveldb/leveldb", - "Rev": "c1f6d721561c48f467b26a277741e55fd224df1e" + "Rev": "614b6126cf79eee8be803e7e65a3ef24fc12e44a" }, { "ImportPath": "github.com/szferi/gomdb", diff --git a/client/ledis-py/ledis/client.py b/client/ledis-py/ledis/client.py index 2504e5d..93245b8 100644 --- a/client/ledis-py/ledis/client.py +++ b/client/ledis-py/ledis/client.py @@ -137,6 +137,7 @@ class Ledis(object): 'PING': lambda r: nativestr(r) == 'PONG', 'SET': lambda r: r and nativestr(r) == 'OK', 'INFO': parse_info, + 'TIME': lambda x: (int(x[0]), int(x[1])), } @@ -1035,6 +1036,13 @@ class Ledis(object): def scriptflush(self): return self.execute_command('SCRIPT', 'FLUSH') + def time(self): + """ + Returns the server time as a 2-item tuple of ints: + (seconds since epoch, microseconds into this second). + """ + return self.execute_command('TIME') + class Transaction(Ledis): def __init__(self, connection_pool, response_callbacks): diff --git a/client/nodejs/ledis/lib/commands.js b/client/nodejs/ledis/lib/commands.js index 8a24f6c..f9c4d6c 100644 --- a/client/nodejs/ledis/lib/commands.js +++ b/client/nodejs/ledis/lib/commands.js @@ -8,6 +8,8 @@ module.exports = [ "info", "flushall", "flushdb", + "time", + "config", "bget", "bdelete", diff --git a/client/openresty/ledis.lua b/client/openresty/ledis.lua index 7834c2b..ddbb150 100644 --- a/client/openresty/ledis.lua +++ b/client/openresty/ledis.lua @@ -153,6 +153,8 @@ local commands = { "info", "flushall", "flushdb", + "time", + "config", -- [[transaction]] "begin", diff --git a/cmd/ledis-benchmark/main.go b/cmd/ledis-benchmark/main.go index f91c98c..9948f5b 100644 --- a/cmd/ledis-benchmark/main.go +++ b/cmd/ledis-benchmark/main.go @@ -17,6 +17,7 @@ var number = flag.Int("n", 1000, "request number") var clients = flag.Int("c", 50, "number of clients") var reverse = flag.Bool("rev", false, "enable zset rev benchmark") var round = flag.Int("r", 1, "benchmark round number") +var del = flag.Bool("del", true, "enable del benchmark") var wg sync.WaitGroup @@ -281,18 +282,27 @@ func main() { benchSet() benchGet() benchRandGet() - benchDel() + + if *del == true { + benchDel() + } benchPushList() benchRangeList10() benchRangeList50() benchRangeList100() - benchPopList() + + if *del == true { + benchPopList() + } benchHset() benchHGet() benchHRandGet() - benchHDel() + + if *del == true { + benchHDel() + } benchZAdd() benchZIncr() @@ -306,7 +316,9 @@ func main() { benchZRevRangeByScore() } - benchZDel() + if *del == true { + benchZDel() + } println("") } diff --git a/cmd/ledis-cli/const.go b/cmd/ledis-cli/const.go index 3bca898..f03b249 100644 --- a/cmd/ledis-cli/const.go +++ b/cmd/ledis-cli/const.go @@ -1,4 +1,4 @@ -//This file was generated by .tools/generate_commands.py on Thu Oct 02 2014 15:24:07 +0800 +//This file was generated by .tools/generate_commands.py on Wed Oct 08 2014 16:36:20 +0800 package main var helpCommands = [][]string{ @@ -16,6 +16,7 @@ var helpCommands = [][]string{ {"BTTL", "key", "Bitmap"}, {"BXSCAN", "key [MATCH match] [COUNT count]", "Bitmap"}, {"COMMIT", "-", "Transaction"}, + {"CONFIG REWRITE", "-", "Server"}, {"DECR", "key", "KV"}, {"DECRBY", "key decrement", "KV"}, {"DEL", "key [key ...]", "KV"}, @@ -96,6 +97,7 @@ var helpCommands = [][]string{ {"SUNIONSTORE", "destination key [key ...]", "Set"}, {"SXSCAN", "key [MATCH match] [COUNT count]", "Set"}, {"SYNC", "logid", "Replication"}, + {"TIME", "-", "Server"}, {"TTL", "key", "KV"}, {"XSCAN", "key [MATCH match] [COUNT count]", "KV"}, {"ZADD", "key score member [score member ...]", "ZSet"}, diff --git a/config/config.go b/config/config.go index 668b545..a90e40c 100644 --- a/config/config.go +++ b/config/config.go @@ -1,11 +1,17 @@ package config import ( + "bytes" + "errors" "github.com/BurntSushi/toml" + "github.com/siddontang/go/ioutil2" + "io" "io/ioutil" ) -type Size int +var ( + ErrNoConfigFile = errors.New("Running without a config file") +) const ( DefaultAddr string = "127.0.0.1:6380" @@ -39,6 +45,8 @@ type ReplicationConfig struct { } type Config struct { + FileName string `toml:"-"` + Addr string `toml:"addr"` HttpAddr string `toml:"http_addr"` @@ -67,7 +75,12 @@ func NewConfigWithFile(fileName string) (*Config, error) { return nil, err } - return NewConfigWithData(data) + if cfg, err := NewConfigWithData(data); err != nil { + return nil, err + } else { + cfg.FileName = fileName + return cfg, nil + } } func NewConfigWithData(data []byte) (*Config, error) { @@ -123,3 +136,27 @@ func (cfg *LevelDBConfig) Adjust() { cfg.MaxOpenFiles = 1024 } } + +func (cfg *Config) Dump(w io.Writer) error { + e := toml.NewEncoder(w) + e.Indent = "" + return e.Encode(cfg) +} + +func (cfg *Config) DumpFile(fileName string) error { + var b bytes.Buffer + + if err := cfg.Dump(&b); err != nil { + return err + } + + return ioutil2.WriteFileAtomic(fileName, b.Bytes(), 0644) +} + +func (cfg *Config) Rewrite() error { + if len(cfg.FileName) == 0 { + return ErrNoConfigFile + } + + return cfg.DumpFile(cfg.FileName) +} diff --git a/config/config_test.go b/config/config_test.go index 47779aa..6bde8bf 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1,13 +1,42 @@ package config import ( + "os" + "reflect" "testing" ) func TestConfig(t *testing.T) { - _, err := NewConfigWithFile("./ledis.toml") + cfg, err := NewConfigWithFile("./config.toml") if err != nil { t.Fatal(err) } + bakFile := "./config.toml.bak" + + defer os.Remove(bakFile) + if err := cfg.DumpFile(bakFile); err != nil { + t.Fatal(err) + } + + if c, err := NewConfigWithFile(bakFile); err != nil { + t.Fatal(err) + } else { + c.FileName = cfg.FileName + if !reflect.DeepEqual(cfg, c) { + t.Fatal("must equal") + } + + c.FileName = bakFile + c.SlaveOf = "127.0.0.1:6381" + if err := c.Rewrite(); err != nil { + t.Fatal(err) + } + + if c1, err := NewConfigWithFile(bakFile); err != nil { + t.Fatal(err) + } else if !reflect.DeepEqual(c, c1) { + t.Fatalf("must equal %v != %v", c, c1) + } + } } diff --git a/doc/commands.json b/doc/commands.json index b813d3b..8268ee8 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -628,6 +628,17 @@ "arguments" : "-", "group": "Script", "readonly": false - } + }, + "TIME": { + "arguments" : "-", + "group": "Server", + "readonly": true + }, + + "CONFIG REWRITE": { + "arguments" : "-", + "group": "Server", + "readonly": false + } } diff --git a/doc/commands.md b/doc/commands.md index 2cae8fd..c01b67c 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -132,6 +132,8 @@ Table of Contents - [FLUSHALL](#flushall) - [FLUSHDB](#flushdb) - [INFO [section]](#info-section) + - [TIME](#time) + - [CONFIG REWRITE](#config-rewrite) - [Transaction](#transaction) - [BEGIN](#begin) - [ROLLBACK](#rollback) @@ -2570,6 +2572,24 @@ The optional parameter can be used to select a specific section of information: When no parameter is provided, all will return. +### TIME + +The TIME command returns the current server time as a two items lists: a Unix timestamp and the amount of microseconds already elapsed in the current second + +**Return value** + +array: two elements, one is unix time in seconds, the other is microseconds. + +### CONFIG REWRITE + +Rewrites the config file the server was started with. + +**Unlike Redis rewrite, it will discard all comments in origin config file.** + +**Return value** + +String: OK or error msg. + ## Transaction ### BEGIN diff --git a/server/cmd_server.go b/server/cmd_server.go index 198003c..d71da77 100644 --- a/server/cmd_server.go +++ b/server/cmd_server.go @@ -2,8 +2,11 @@ package server import ( "github.com/siddontang/go/hack" + "github.com/siddontang/go/num" + "strconv" "strings" + "time" ) func pingCommand(c *client) error { @@ -85,6 +88,47 @@ func flushdbCommand(c *client) error { return nil } +func timeCommand(c *client) error { + if len(c.args) != 0 { + return ErrCmdParams + } + + t := time.Now() + + //seconds + s := t.Unix() + n := t.UnixNano() + + //micro seconds + m := (n - s*1e9) / 1e3 + + ay := []interface{}{ + num.FormatInt64ToSlice(s), + num.FormatInt64ToSlice(m), + } + + c.resp.writeArray(ay) + return nil +} + +func configCommand(c *client) error { + if len(c.args) < 1 { + return ErrCmdParams + } + + switch strings.ToLower(hack.String(c.args[0])) { + case "rewrite": + if err := c.app.cfg.Rewrite(); err != nil { + return err + } else { + c.resp.writeStatus(OK) + return nil + } + default: + return ErrCmdParams + } +} + func init() { register("ping", pingCommand) register("echo", echoCommand) @@ -92,4 +136,6 @@ func init() { register("info", infoCommand) register("flushall", flushallCommand) register("flushdb", flushdbCommand) + register("time", timeCommand) + register("config", configCommand) } diff --git a/tools/redis_import/README.md b/tools/redis_import/README.md index 634ed60..e93f847 100644 --- a/tools/redis_import/README.md +++ b/tools/redis_import/README.md @@ -1,6 +1,5 @@ ## Notice -1. The tool doesn't support `set` data type. 2. The tool doesn't support `bitmap` data type. 2. Our `zset` use integer instead of double, so the zset float score in Redis will be **converted to integer**. diff --git a/tools/redis_import/redis_import.py b/tools/redis_import/redis_import.py index 7a463ae..7036f89 100644 --- a/tools/redis_import/redis_import.py +++ b/tools/redis_import/redis_import.py @@ -35,7 +35,7 @@ def set_ttl(redis_client, ledis_client, key, k_type): "string": ledis_client.expire, "list": ledis_client.lexpire, "hash": ledis_client.hexpire, - "set": ledis_client.zexpire, + "set": ledis_client.sexpire, "zset": ledis_client.zexpire } timeout = redis_client.ttl(key) @@ -46,6 +46,7 @@ def set_ttl(redis_client, ledis_client, key, k_type): def copy_key(redis_client, ledis_client, key, convert=False): global entries k_type = redis_client.type(key) + if k_type == "string": value = redis_client.get(key) ledis_client.set(key, value) @@ -66,6 +67,7 @@ def copy_key(redis_client, ledis_client, key, convert=False): entries += 1 elif k_type == "zset": + # dangerous to do this? out = redis_client.zrange(key, 0, -1, withscores=True) pieces = od() for i in od(out).iteritems(): @@ -74,6 +76,14 @@ def copy_key(redis_client, ledis_client, key, convert=False): set_ttl(redis_client, ledis_client, key, k_type) entries += 1 + elif k_type == "set": + mbs = list(redis_client.smembers(key)) + + if mbs is not None: + ledis_client.sadd(key, *mbs) + set_ttl(redis_client, ledis_client, key, k_type) + entries += 1 + else: print "KEY %s of TYPE %s is not supported by LedisDB." % (key, k_type) diff --git a/tools/redis_import/test.py b/tools/redis_import/test.py index 96cceeb..0279c26 100644 --- a/tools/redis_import/test.py +++ b/tools/redis_import/test.py @@ -47,6 +47,9 @@ def random_zset(client, words, length=1000): client.zadd("zsetName", **d) +def random_set(client, words, length=1000): + client.sadd("setName", *words) + def test(): words = get_words() print "Flush all redis data before insert new." @@ -64,10 +67,9 @@ def test(): random_zset(rds, words) print "random_zset done" + random_set(rds, words) + print "random_set done" - lds.lclear("listName") - lds.hclear("hashName") - lds.zclear("zsetName") copy(rds, lds, convert=True) # for all keys @@ -101,6 +103,7 @@ def ledis_ttl(ledis_client, key, k_type): "list": lds.lttl, "hash": lds.httl, "zset": lds.zttl, + "set": lds.sttl, } return ttls[k_type](key) @@ -115,6 +118,9 @@ def test_ttl(): assert ledis_ttl(lds, key, k_type) > 0 if __name__ == "__main__": + rds.flushdb() + lds.flushdb() + test() test_ttl() print "Test passed."