├── nsqd ├── test │ ├── certs │ │ ├── ca.srl │ │ ├── server.key │ │ ├── ca.key │ │ ├── client.req │ │ ├── client.pem │ │ ├── server.pem │ │ ├── ca.pem │ │ ├── cert.pem │ │ ├── client.key │ │ └── key.pem │ ├── openssl.conf │ └── cert.sh ├── context.go ├── logger.go ├── README.md ├── rename.go ├── dqname.go ├── backend_queue.go ├── dqname_windows.go ├── buffer_pool.go ├── guid_test.go ├── dummy_backend_queue.go ├── rename_windows.go ├── tcp.go ├── in_flight_pqueue_test.go ├── in_flight_pqueue.go ├── guid.go ├── rename_windows_test.go ├── stats_test.go ├── message.go ├── lookup_peer.go └── options.go ├── nsqadmin ├── .gitignore ├── gulp ├── context.go ├── static │ ├── img │ │ ├── favicon.png │ │ └── nsq_blue.png │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── js │ │ ├── views │ │ │ ├── spinner.hbs │ │ │ ├── error.hbs │ │ │ ├── warning.hbs │ │ │ ├── node.js │ │ │ ├── topics.js │ │ │ ├── header.js │ │ │ ├── nodes.js │ │ │ ├── topics.hbs │ │ │ ├── topic.js │ │ │ ├── header.hbs │ │ │ ├── nodes.hbs │ │ │ ├── channel.js │ │ │ ├── lookup.hbs │ │ │ ├── lookup.js │ │ │ ├── base.js │ │ │ ├── counter.js │ │ │ └── counter.hbs │ │ ├── lib │ │ │ ├── pubsub.js │ │ │ └── ajax_setup.js │ │ ├── main.js │ │ ├── collections │ │ │ ├── nodes.js │ │ │ └── topics.js │ │ ├── models │ │ │ ├── node.js │ │ │ ├── topic.js │ │ │ └── channel.js │ │ ├── app_state.js │ │ └── router.js │ ├── html │ │ └── index.html │ └── css │ │ └── base.scss ├── logger.go ├── test │ ├── ca-key.pem │ ├── client-key.pem │ ├── server-key.pem │ ├── ca.pem │ ├── client.pem │ └── server.pem ├── README.md ├── package.json ├── options.go ├── notify.go ├── .eslintrc ├── nsqadmin_test.go └── gulpfile.js ├── fmt.sh ├── bench ├── requirements.txt ├── bench_channels │ └── bench_channels.go ├── bench_writer │ └── bench_writer.go └── bench_reader │ └── bench_reader.go ├── nsqlookupd ├── context.go ├── logger.go ├── README.md ├── client_v1.go ├── options.go ├── tcp.go ├── lookup_protocol_v1_test.go ├── nsqlookupd.go └── registration_db_test.go ├── internal ├── app │ ├── logger.go │ ├── string_array.go │ └── float_array.go ├── statsd │ ├── host.go │ └── client.go ├── version │ └── binary.go ├── util │ ├── wait_group_wrapper.go │ └── rand.go ├── dirlock │ ├── dirlock_windows.go │ └── dirlock.go ├── test │ ├── logger.go │ ├── assertions.go │ └── fakes.go ├── protocol │ ├── byte_base10.go │ ├── byte_base10_test.go │ ├── names.go │ ├── tcp_server.go │ ├── protocol.go │ └── errors.go ├── http_api │ ├── topic_channel_args.go │ ├── http_server.go │ ├── req_params.go │ └── compress.go ├── stringy │ ├── slice.go │ └── template.go ├── pqueue │ ├── pqueue.go │ └── pqueue_test.go ├── quantile │ ├── aggregate.go │ └── quantile.go └── auth │ └── authorizations.go ├── apps ├── nsqd │ ├── README.md │ └── nsqd_test.go ├── nsqlookupd │ ├── README.md │ └── nsqlookupd.go ├── nsq_pubsub │ └── README.md ├── to_nsq │ ├── README.md │ └── to_nsq.go ├── nsq_to_http │ └── http.go ├── nsq_to_file │ └── strftime.go ├── nsq_tail │ └── nsq_tail.go └── nsqadmin │ └── main.go ├── Dockerfile ├── .travis.yml ├── test.sh ├── contrib ├── nsqlookupd.cfg.example ├── nsqadmin.cfg.example ├── nsq.spec └── nsqd.cfg.example ├── .gitignore ├── Godeps ├── LICENSE ├── bench.sh ├── coverage.sh ├── Makefile ├── CONTRIBUTING.md ├── dist.sh └── CODE_OF_CONDUCT.md /nsqd/test/certs/ca.srl: -------------------------------------------------------------------------------- 1 | 02 2 | -------------------------------------------------------------------------------- /nsqadmin/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /fmt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | find . -name "*.go" | xargs goimports -w 3 | -------------------------------------------------------------------------------- /nsqadmin/gulp: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | exec ./node_modules/.bin/gulp $@ 3 | -------------------------------------------------------------------------------- /nsqd/test/openssl.conf: -------------------------------------------------------------------------------- 1 | [ ssl_client ] 2 | extendedKeyUsage = clientAuth -------------------------------------------------------------------------------- /bench/requirements.txt: -------------------------------------------------------------------------------- 1 | tornado==4.3 2 | paramiko==1.16.0 3 | boto==2.38.0 4 | -------------------------------------------------------------------------------- /nsqd/context.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | type context struct { 4 | nsqd *NSQD 5 | } 6 | -------------------------------------------------------------------------------- /nsqadmin/context.go: -------------------------------------------------------------------------------- 1 | package nsqadmin 2 | 3 | type Context struct { 4 | nsqadmin *NSQAdmin 5 | } 6 | -------------------------------------------------------------------------------- /nsqadmin/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkgs/nsq/master/nsqadmin/static/img/favicon.png -------------------------------------------------------------------------------- /nsqadmin/static/img/nsq_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkgs/nsq/master/nsqadmin/static/img/nsq_blue.png -------------------------------------------------------------------------------- /nsqd/logger.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | type Logger interface { 4 | Output(maxdepth int, s string) error 5 | } 6 | -------------------------------------------------------------------------------- /nsqlookupd/context.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd 2 | 3 | type Context struct { 4 | nsqlookupd *NSQLookupd 5 | } 6 | -------------------------------------------------------------------------------- /internal/app/logger.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | type Logger interface { 4 | Output(maxdepth int, s string) error 5 | } 6 | -------------------------------------------------------------------------------- /nsqadmin/logger.go: -------------------------------------------------------------------------------- 1 | package nsqadmin 2 | 3 | type Logger interface { 4 | Output(maxdepth int, s string) error 5 | } 6 | -------------------------------------------------------------------------------- /nsqlookupd/logger.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd 2 | 3 | type Logger interface { 4 | Output(maxdepth int, s string) error 5 | } 6 | -------------------------------------------------------------------------------- /nsqadmin/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkgs/nsq/master/nsqadmin/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /nsqadmin/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkgs/nsq/master/nsqadmin/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /nsqadmin/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkgs/nsq/master/nsqadmin/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /nsqadmin/static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkgs/nsq/master/nsqadmin/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /nsqd/README.md: -------------------------------------------------------------------------------- 1 | ## nsqd 2 | 3 | `nsqd` is the daemon that receives, queues, and delivers messages to clients. 4 | 5 | Read the [docs](http://nsq.io/components/nsqd.html) 6 | -------------------------------------------------------------------------------- /apps/nsqd/README.md: -------------------------------------------------------------------------------- 1 | ## nsqd 2 | 3 | `nsqd` is the daemon that receives, queues, and delivers messages to clients. 4 | 5 | Read the [docs](http://nsq.io/components/nsqd.html). 6 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/spinner.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | -------------------------------------------------------------------------------- /internal/statsd/host.go: -------------------------------------------------------------------------------- 1 | package statsd 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | func HostKey(h string) string { 8 | return strings.Replace(strings.Replace(h, ".", "_", -1), ":", "_", -1) 9 | } 10 | -------------------------------------------------------------------------------- /nsqd/rename.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package nsqd 4 | 5 | import ( 6 | "os" 7 | ) 8 | 9 | func atomicRename(sourceFile, targetFile string) error { 10 | return os.Rename(sourceFile, targetFile) 11 | } 12 | -------------------------------------------------------------------------------- /nsqlookupd/README.md: -------------------------------------------------------------------------------- 1 | ## nsqlookupd 2 | 3 | `nsqlookupd` is the daemon that manages topology metadata and serves client requests to 4 | discover the location of topics at runtime. 5 | 6 | Read the [docs](http://nsq.io/components/nsqlookupd.html) 7 | -------------------------------------------------------------------------------- /apps/nsqlookupd/README.md: -------------------------------------------------------------------------------- 1 | ## nsqlookupd 2 | 3 | `nsqlookupd` is the daemon that manages topology metadata and serves client requests to 4 | discover the location of topics at runtime. 5 | 6 | Read the [docs](http://nsq.io/components/nsqlookupd.html). 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.4 2 | 3 | EXPOSE 4150 4151 4160 4161 4170 4171 4 | 5 | VOLUME /data 6 | VOLUME /etc/ssl/certs 7 | 8 | COPY dist/docker/bin/ /usr/local/bin/ 9 | RUN ln -s /usr/local/bin/*nsq* / \ 10 | && ln -s /usr/local/bin/*nsq* /bin/ 11 | -------------------------------------------------------------------------------- /internal/version/binary.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | ) 7 | 8 | const Binary = "0.3.8" 9 | 10 | func String(app string) string { 11 | return fmt.Sprintf("%s v%s (built w/%s)", app, Binary, runtime.Version()) 12 | } 13 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/error.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {{message}} 5 |
6 |
7 |
8 | -------------------------------------------------------------------------------- /nsqadmin/test/ca-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIPX0F2UAFnFvtMjCWzT4THHDe4hacvJKdmwt2ws5ETuroAoGCCqGSM49 3 | AwEHoUQDQgAEeOVIlOAbueg0v9JSCcjox4Yk2XcKtxaj4T1GpEwW7wgZ9028sDxV 4 | 0BB0ChpFVUN6IBH704KcEEdnr3E3VnmU/A== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/warning.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {{message}} 5 |
6 |
7 |
8 | -------------------------------------------------------------------------------- /nsqadmin/test/client-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEINc+n9KobI9A+wKEHULHUgTcoBOUoNjU2BgE8fH8Se/uoAoGCCqGSM49 3 | AwEHoUQDQgAE3X+ggUCwjyt3267kU5rhD9KHhJCJuhJOtOmXs62qiXE1yqkJrVND 4 | 4AdkzQXb3KNuk063/dx98ICobBWH9AQWsQ== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /nsqadmin/test/server-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEICLhO4x5g6cjCa69F64epaWTDEnIvmR9+VNa76vvAlqWoAoGCCqGSM49 3 | AwEHoUQDQgAE6BYZCfbbo8fyAiMD56Io0PucqeFEyg3Pp2bunb2yzoo1CcoohqC/ 4 | ISQw1/MNsVRvujEOGSZHmeuB72zbNV+Myg== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /internal/util/wait_group_wrapper.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type WaitGroupWrapper struct { 8 | sync.WaitGroup 9 | } 10 | 11 | func (w *WaitGroupWrapper) Wrap(cb func()) { 12 | w.Add(1) 13 | go func() { 14 | cb() 15 | w.Done() 16 | }() 17 | } 18 | -------------------------------------------------------------------------------- /nsqadmin/static/js/lib/pubsub.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | var Backbone = require('backbone'); 3 | 4 | var Pubsub = _.clone(Backbone.Events); 5 | 6 | // making this global to more easily trigger events from the console 7 | window.Pubsub = Pubsub; 8 | 9 | module.exports = Pubsub; 10 | -------------------------------------------------------------------------------- /nsqd/dqname.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package nsqd 4 | 5 | func getBackendName(topicName, channelName string) string { 6 | // backend names, for uniqueness, automatically include the topic... : 7 | backendName := topicName + ":" + channelName 8 | return backendName 9 | } 10 | -------------------------------------------------------------------------------- /internal/app/string_array.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type StringArray []string 8 | 9 | func (a *StringArray) Set(s string) error { 10 | *a = append(*a, s) 11 | return nil 12 | } 13 | 14 | func (a *StringArray) String() string { 15 | return strings.Join(*a, ",") 16 | } 17 | -------------------------------------------------------------------------------- /nsqd/backend_queue.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | // BackendQueue represents the behavior for the secondary message 4 | // storage system 5 | type BackendQueue interface { 6 | Put([]byte) error 7 | ReadChan() chan []byte // this is expected to be an *unbuffered* channel 8 | Close() error 9 | Delete() error 10 | Depth() int64 11 | Empty() error 12 | } 13 | -------------------------------------------------------------------------------- /nsqd/dqname_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package nsqd 4 | 5 | // On Windows, file names cannot contain colons. 6 | func getBackendName(topicName, channelName string) string { 7 | // backend names, for uniqueness, automatically include the topic... ; 8 | backendName := topicName + ";" + channelName 9 | return backendName 10 | } 11 | -------------------------------------------------------------------------------- /nsqlookupd/client_v1.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | type ClientV1 struct { 8 | net.Conn 9 | peerInfo *PeerInfo 10 | } 11 | 12 | func NewClientV1(conn net.Conn) *ClientV1 { 13 | return &ClientV1{ 14 | Conn: conn, 15 | } 16 | } 17 | 18 | func (c *ClientV1) String() string { 19 | return c.RemoteAddr().String() 20 | } 21 | -------------------------------------------------------------------------------- /internal/dirlock/dirlock_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package dirlock 4 | 5 | type DirLock struct { 6 | dir string 7 | } 8 | 9 | func New(dir string) *DirLock { 10 | return &DirLock{ 11 | dir: dir, 12 | } 13 | } 14 | 15 | func (l *DirLock) Lock() error { 16 | return nil 17 | } 18 | 19 | func (l *DirLock) Unlock() error { 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /nsqd/buffer_pool.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | import ( 4 | "bytes" 5 | "sync" 6 | ) 7 | 8 | var bp sync.Pool 9 | 10 | func init() { 11 | bp.New = func() interface{} { 12 | return &bytes.Buffer{} 13 | } 14 | } 15 | 16 | func bufferPoolGet() *bytes.Buffer { 17 | return bp.Get().(*bytes.Buffer) 18 | } 19 | 20 | func bufferPoolPut(b *bytes.Buffer) { 21 | bp.Put(b) 22 | } 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.6.3 4 | - 1.7.3 5 | env: 6 | - GOARCH=amd64 7 | - GOARCH=386 8 | sudo: false 9 | before_install: 10 | - go get github.com/mattn/goveralls 11 | script: 12 | - curl -s https://raw.githubusercontent.com/pote/gpm/v1.4.0/bin/gpm > gpm 13 | - chmod +x gpm 14 | - ./gpm install 15 | - ./test.sh 16 | - ./coverage.sh --coveralls 17 | notifications: 18 | email: false 19 | -------------------------------------------------------------------------------- /internal/test/logger.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/nsqio/nsq/internal/app" 5 | ) 6 | 7 | type tbLog interface { 8 | Log(...interface{}) 9 | } 10 | 11 | type testLogger struct { 12 | tbLog 13 | } 14 | 15 | func (tl *testLogger) Output(maxdepth int, s string) error { 16 | tl.Log(s) 17 | return nil 18 | } 19 | 20 | func NewTestLogger(tbl tbLog) app.Logger { 21 | return &testLogger{tbl} 22 | } 23 | -------------------------------------------------------------------------------- /internal/util/rand.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "math/rand" 5 | ) 6 | 7 | func UniqRands(l int, n int) []int { 8 | set := make(map[int]struct{}) 9 | nums := make([]int, 0, l) 10 | for { 11 | num := rand.Intn(n) 12 | if _, ok := set[num]; !ok { 13 | set[num] = struct{}{} 14 | nums = append(nums, num) 15 | } 16 | if len(nums) == l { 17 | goto exit 18 | } 19 | } 20 | exit: 21 | return nums 22 | } 23 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | GOMAXPROCS=1 go test -timeout 90s ./... 5 | GOMAXPROCS=4 go test -timeout 90s -race ./... 6 | 7 | # no tests, but a build is something 8 | for dir in $(find apps bench -maxdepth 1 -type d) nsqadmin; do 9 | if grep -q '^package main$' $dir/*.go 2>/dev/null; then 10 | echo "building $dir" 11 | go build -o $dir/$(basename $dir) ./$dir 12 | else 13 | echo "(skipped $dir)" 14 | fi 15 | done 16 | -------------------------------------------------------------------------------- /nsqadmin/static/js/lib/ajax_setup.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | var _ = require('underscore'); 3 | 4 | // Set up some headers and options for every request. 5 | $.ajaxPrefilter(function(options) { 6 | options['headers'] = _.defaults(options['headers'] || {}, { 7 | 'X-UserAgent': USER_AGENT, 8 | 'Accept': 'application/vnd.nsq; version=1.0' 9 | }); 10 | options['timeout'] = 20 * 1000; 11 | options['contentType'] = 'application/json'; 12 | }); 13 | -------------------------------------------------------------------------------- /internal/protocol/byte_base10.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var errBase10 = errors.New("failed to convert to Base10") 8 | 9 | func ByteToBase10(b []byte) (n uint64, err error) { 10 | base := uint64(10) 11 | 12 | n = 0 13 | for i := 0; i < len(b); i++ { 14 | var v byte 15 | d := b[i] 16 | switch { 17 | case '0' <= d && d <= '9': 18 | v = d - '0' 19 | default: 20 | n = 0 21 | err = errBase10 22 | return 23 | } 24 | n *= base 25 | n += uint64(v) 26 | } 27 | 28 | return n, err 29 | } 30 | -------------------------------------------------------------------------------- /internal/protocol/byte_base10_test.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var result uint64 8 | 9 | func BenchmarkByteToBase10Valid(b *testing.B) { 10 | bt := []byte{'3', '1', '4', '1', '5', '9', '2', '5'} 11 | var n uint64 12 | for i := 0; i < b.N; i++ { 13 | n, _ = ByteToBase10(bt) 14 | } 15 | result = n 16 | } 17 | 18 | func BenchmarkByteToBase10Invalid(b *testing.B) { 19 | bt := []byte{'?', '1', '4', '1', '5', '9', '2', '5'} 20 | var n uint64 21 | for i := 0; i < b.N; i++ { 22 | n, _ = ByteToBase10(bt) 23 | } 24 | result = n 25 | } 26 | -------------------------------------------------------------------------------- /nsqadmin/test/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBXjCCAQSgAwIBAgIUXA9F8KjsPh7MFLGofbLLuiIo2G4wCgYIKoZIzj0EAwIw 3 | DTELMAkGA1UEAxMCY2EwHhcNMTYwOTA5MjIyMTAwWhcNMjEwOTA4MjIyMTAwWjAN 4 | MQswCQYDVQQDEwJjYTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHjlSJTgG7no 5 | NL/SUgnI6MeGJNl3CrcWo+E9RqRMFu8IGfdNvLA8VdAQdAoaRVVDeiAR+9OCnBBH 6 | Z69xN1Z5lPyjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0G 7 | A1UdDgQWBBS+d0ybr0D2iw12mg1vsudjbrPctDAKBggqhkjOPQQDAgNIADBFAiB1 8 | ptdrFGkY/hlBjOwigsQdv916HuYJgwOlLyaKttVudAIhAKLYmVrjraUx7uPjz9cZ 9 | O6wQnCtmwwlEQtcXpGlQkfGV 10 | -----END CERTIFICATE----- 11 | -------------------------------------------------------------------------------- /contrib/nsqlookupd.cfg.example: -------------------------------------------------------------------------------- 1 | ## enable verbose logging 2 | verbose = false 3 | 4 | 5 | ## : to listen on for TCP clients 6 | tcp_address = "0.0.0.0:4160" 7 | 8 | ## : to listen on for HTTP clients 9 | http_address = "0.0.0.0:4161" 10 | 11 | ## address that will be registered with lookupd (defaults to the OS hostname) 12 | # broadcast_address = "" 13 | 14 | 15 | ## duration of time a producer will remain in the active list since its last ping 16 | inactive_producer_timeout = "300s" 17 | 18 | ## duration of time a producer will remain tombstoned if registration remains 19 | tombstone_lifetime = "45s" 20 | -------------------------------------------------------------------------------- /nsqadmin/README.md: -------------------------------------------------------------------------------- 1 | ## nsqadmin 2 | 3 | `nsqadmin` is a Web UI to view aggregated cluster stats in realtime and perform various 4 | administrative tasks. 5 | 6 | Read the [docs](http://nsq.io/components/nsqadmin.html) 7 | 8 | ## Working Locally 9 | 10 | 1. `$ npm install` 11 | 2. `$ ./gulp clean watch` or `$ ./gulp clean build` 12 | 3. `$ go-bindata --debug --pkg=nsqadmin --prefix=static/build static/build/...` 13 | 4. `$ go build && ./nsqadmin` 14 | 5. make changes (repeat step 5 if you make changes to any Go code) 15 | 6. `$ go-bindata --pkg=nsqadmin --prefix=static/build static/build/...` 16 | 7. commit other changes and `bindata.go` 17 | -------------------------------------------------------------------------------- /nsqadmin/static/js/main.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | var Backbone = require('backbone'); 3 | 4 | var Pubsub = require('./lib/pubsub'); 5 | var Router = require('./router'); 6 | 7 | var AppView = require('./views/app'); 8 | 9 | // When using browserify, we need to tell Backbone what jQuery to use. 10 | Backbone.$ = $; 11 | 12 | // Side effects: 13 | require('./lib/ajax_setup'); 14 | require('./lib/handlebars_helpers'); 15 | 16 | var start = function() { 17 | new AppView(); 18 | Router.start(); 19 | }; 20 | 21 | // Pubsub.on('all', function() { 22 | // console.log.apply(console, arguments); 23 | // }); 24 | 25 | start(); 26 | -------------------------------------------------------------------------------- /nsqadmin/static/js/collections/nodes.js: -------------------------------------------------------------------------------- 1 | var Backbone = require('backbone'); 2 | 3 | var AppState = require('../app_state'); 4 | 5 | var Node = require('../models/node'); //eslint-disable-line no-undef 6 | 7 | var Nodes = Backbone.Collection.extend({ 8 | model: Node, 9 | 10 | comparator: 'id', 11 | 12 | constructor: function Nodes() { 13 | Backbone.Collection.prototype.constructor.apply(this, arguments); 14 | }, 15 | 16 | url: function() { 17 | return AppState.url('/nodes'); 18 | }, 19 | 20 | parse: function(resp) { 21 | return resp['nodes']; 22 | } 23 | }); 24 | 25 | module.exports = Nodes; 26 | -------------------------------------------------------------------------------- /internal/protocol/names.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | var validTopicChannelNameRegex = regexp.MustCompile(`^[\.a-zA-Z0-9_-]+(#ephemeral)?$`) 8 | 9 | // IsValidTopicName checks a topic name for correctness 10 | func IsValidTopicName(name string) bool { 11 | return isValidName(name) 12 | } 13 | 14 | // IsValidChannelName checks a channel name for correctness 15 | func IsValidChannelName(name string) bool { 16 | return isValidName(name) 17 | } 18 | 19 | func isValidName(name string) bool { 20 | if len(name) > 64 || len(name) < 1 { 21 | return false 22 | } 23 | return validTopicChannelNameRegex.MatchString(name) 24 | } 25 | -------------------------------------------------------------------------------- /nsqadmin/static/js/models/node.js: -------------------------------------------------------------------------------- 1 | var AppState = require('../app_state'); 2 | var Backbone = require('backbone'); 3 | 4 | var Node = Backbone.Model.extend({ //eslint-disable-line no-undef 5 | idAttribute: 'name', 6 | 7 | constructor: function Node() { 8 | Backbone.Model.prototype.constructor.apply(this, arguments); 9 | }, 10 | 11 | urlRoot: function() { 12 | return AppState.url('/nodes'); 13 | }, 14 | 15 | tombstoneTopic: function(topic) { 16 | return this.destroy({ 17 | 'data': JSON.stringify({'topic': topic}), 18 | 'dataType': 'text' 19 | }); 20 | } 21 | }); 22 | 23 | module.exports = Node; 24 | -------------------------------------------------------------------------------- /nsqadmin/test/client.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBojCCAUigAwIBAgIUHNsyTV47b/ycrgSoqYpyBZ9CT9AwCgYIKoZIzj0EAwIw 3 | DTELMAkGA1UEAxMCY2EwHhcNMTYwOTA5MjIyNzAwWhcNMTcwOTA5MjIyNzAwWjAU 4 | MRIwEAYDVQQDEwkxMjcuMC4wLjEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATd 5 | f6CBQLCPK3fbruRTmuEP0oeEkIm6Ek606ZezraqJcTXKqQmtU0PgB2TNBdvco26T 6 | Trf93H3wgKhsFYf0BBaxo38wfTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYI 7 | KwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFNmWvdcp 8 | X3Gof9WxCc5S0KyqaK4kMB8GA1UdIwQYMBaAFL53TJuvQPaLDXaaDW+y52Nus9y0 9 | MAoGCCqGSM49BAMCA0gAMEUCIQCcdqM8MSawTyvmjCmm2VoLsJIaIH68MVt7t5z1 10 | ymt5cAIgfoJ6UFwbzrBDPhAsPS02tP+C6XTSsIi0c+LziFuDtpo= 11 | -----END CERTIFICATE----- 12 | -------------------------------------------------------------------------------- /apps/nsq_pubsub/README.md: -------------------------------------------------------------------------------- 1 | nsq_pubsub 2 | ========== 3 | 4 | This provides a HTTP streaming interface to nsq channels. 5 | 6 | 7 | /sub?topic=....&channel=.... 8 | 9 | Each connection will get heartbeats pushed to it in the following format every 30 seconds 10 | 11 | {"_heartbeat_":1343400473} 12 | 13 | There is also a stats endpoint which will list out information about connected clients 14 | 15 | /stats 16 | 17 | It's output looks like this. Total messages is the count of messages delivered to HTTP clients since startup. 18 | 19 | Total Messages: 0 20 | 21 | [127.0.0.1:50386] [test_topic : test_channel] msgs: 0 fin: 0 re-q: 0 connected: 3s 22 | 23 | -------------------------------------------------------------------------------- /nsqadmin/test/server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBtDCCAVugAwIBAgIUMKZLN61TkAOnvAJQJKbLQoMKCYYwCgYIKoZIzj0EAwIw 3 | DTELMAkGA1UEAxMCY2EwHhcNMTYwOTA5MjIyNzAwWhcNMTcwOTA5MjIyNzAwWjAU 4 | MRIwEAYDVQQDEwkxMjcuMC4wLjEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATo 5 | FhkJ9tujx/ICIwPnoijQ+5yp4UTKDc+nZu6dvbLOijUJyiiGoL8hJDDX8w2xVG+6 6 | MQ4ZJkeZ64HvbNs1X4zKo4GRMIGOMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAU 7 | BggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUiER1 8 | tUKc7zdm01+F4hpQd93Q214wHwYDVR0jBBgwFoAUvndMm69A9osNdpoNb7LnY26z 9 | 3LQwDwYDVR0RBAgwBocEfwAAATAKBggqhkjOPQQDAgNHADBEAiBnfVH+VAQgf/m2 10 | 28BvMHv6jL+pnlrmVDmtpV9N3CrraAIgcWjvOOU1/q4TT0a7g8o4cx7LS4XAm3fz 11 | hi91xiY985c= 12 | -----END CERTIFICATE----- 13 | -------------------------------------------------------------------------------- /nsqd/guid_test.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | import ( 4 | "testing" 5 | "unsafe" 6 | ) 7 | 8 | func BenchmarkGUIDCopy(b *testing.B) { 9 | source := make([]byte, 16) 10 | var dest MessageID 11 | for i := 0; i < b.N; i++ { 12 | copy(dest[:], source) 13 | } 14 | } 15 | 16 | func BenchmarkGUIDUnsafe(b *testing.B) { 17 | source := make([]byte, 16) 18 | var dest MessageID 19 | for i := 0; i < b.N; i++ { 20 | dest = *(*MessageID)(unsafe.Pointer(&source[0])) 21 | } 22 | _ = dest 23 | } 24 | 25 | func BenchmarkGUID(b *testing.B) { 26 | factory := &guidFactory{} 27 | for i := 0; i < b.N; i++ { 28 | guid, err := factory.NewGUID(0) 29 | if err != nil { 30 | continue 31 | } 32 | guid.Hex() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /nsqd/dummy_backend_queue.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | type dummyBackendQueue struct { 4 | readChan chan []byte 5 | } 6 | 7 | func newDummyBackendQueue() BackendQueue { 8 | return &dummyBackendQueue{readChan: make(chan []byte)} 9 | } 10 | 11 | func (d *dummyBackendQueue) Put([]byte) error { 12 | return nil 13 | } 14 | 15 | func (d *dummyBackendQueue) ReadChan() chan []byte { 16 | return d.readChan 17 | } 18 | 19 | func (d *dummyBackendQueue) Close() error { 20 | return nil 21 | } 22 | 23 | func (d *dummyBackendQueue) Delete() error { 24 | return nil 25 | } 26 | 27 | func (d *dummyBackendQueue) Depth() int64 { 28 | return int64(0) 29 | } 30 | 31 | func (d *dummyBackendQueue) Empty() error { 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /internal/dirlock/dirlock.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package dirlock 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "syscall" 9 | ) 10 | 11 | type DirLock struct { 12 | dir string 13 | f *os.File 14 | } 15 | 16 | func New(dir string) *DirLock { 17 | return &DirLock{ 18 | dir: dir, 19 | } 20 | } 21 | 22 | func (l *DirLock) Lock() error { 23 | f, err := os.Open(l.dir) 24 | if err != nil { 25 | return err 26 | } 27 | l.f = f 28 | err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) 29 | if err != nil { 30 | return fmt.Errorf("cannot flock directory %s - %s", l.dir, err) 31 | } 32 | return nil 33 | } 34 | 35 | func (l *DirLock) Unlock() error { 36 | defer l.f.Close() 37 | return syscall.Flock(int(l.f.Fd()), syscall.LOCK_UN) 38 | } 39 | -------------------------------------------------------------------------------- /nsqadmin/static/js/collections/topics.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | var Backbone = require('backbone'); 3 | 4 | var AppState = require('../app_state'); 5 | 6 | var Topic = require('../models/topic'); 7 | 8 | var Topics = Backbone.Collection.extend({ 9 | model: Topic, 10 | 11 | comparator: 'id', 12 | 13 | constructor: function Topics() { 14 | Backbone.Collection.prototype.constructor.apply(this, arguments); 15 | }, 16 | 17 | url: function() { 18 | return AppState.url('/topics'); 19 | }, 20 | 21 | parse: function(resp) { 22 | var topics = _.map(resp['topics'], function(name) { 23 | return {'name': name}; 24 | }); 25 | return topics; 26 | } 27 | }); 28 | 29 | module.exports = Topics; 30 | -------------------------------------------------------------------------------- /apps/nsqd/nsqd_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "os" 6 | "testing" 7 | 8 | "github.com/BurntSushi/toml" 9 | "github.com/mreiferson/go-options" 10 | "github.com/nsqio/nsq/nsqd" 11 | ) 12 | 13 | func TestConfigFlagParsing(t *testing.T) { 14 | opts := nsqd.NewOptions() 15 | 16 | flagSet := nsqdFlagSet(opts) 17 | flagSet.Parse([]string{}) 18 | 19 | var cfg config 20 | f, err := os.Open("../../contrib/nsqd.cfg.example") 21 | if err != nil { 22 | t.Fatalf("%s", err) 23 | } 24 | toml.DecodeReader(f, &cfg) 25 | cfg.Validate() 26 | 27 | options.Resolve(opts, flagSet, cfg) 28 | nsqd.New(opts) 29 | 30 | if opts.TLSMinVersion != tls.VersionTLS10 { 31 | t.Errorf("min %#v not expected %#v", opts.TLSMinVersion, tls.VersionTLS10) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /internal/http_api/topic_channel_args.go: -------------------------------------------------------------------------------- 1 | package http_api 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/nsqio/nsq/internal/protocol" 7 | ) 8 | 9 | type getter interface { 10 | Get(key string) (string, error) 11 | } 12 | 13 | func GetTopicChannelArgs(rp getter) (string, string, error) { 14 | topicName, err := rp.Get("topic") 15 | if err != nil { 16 | return "", "", errors.New("MISSING_ARG_TOPIC") 17 | } 18 | 19 | if !protocol.IsValidTopicName(topicName) { 20 | return "", "", errors.New("INVALID_ARG_TOPIC") 21 | } 22 | 23 | channelName, err := rp.Get("channel") 24 | if err != nil { 25 | return "", "", errors.New("MISSING_ARG_CHANNEL") 26 | } 27 | 28 | if !protocol.IsValidChannelName(channelName) { 29 | return "", "", errors.New("INVALID_ARG_CHANNEL") 30 | } 31 | 32 | return topicName, channelName, nil 33 | } 34 | -------------------------------------------------------------------------------- /internal/stringy/slice.go: -------------------------------------------------------------------------------- 1 | package stringy 2 | 3 | func Add(s []string, a string) []string { 4 | for _, existing := range s { 5 | if a == existing { 6 | return s 7 | } 8 | } 9 | return append(s, a) 10 | 11 | } 12 | 13 | func Union(s []string, a []string) []string { 14 | for _, entry := range a { 15 | found := false 16 | for _, existing := range s { 17 | if entry == existing { 18 | found = true 19 | break 20 | } 21 | } 22 | if !found { 23 | s = append(s, entry) 24 | } 25 | } 26 | return s 27 | } 28 | 29 | func Uniq(s []string) (r []string) { 30 | for _, entry := range s { 31 | found := false 32 | for _, existing := range r { 33 | if existing == entry { 34 | found = true 35 | break 36 | } 37 | } 38 | if !found { 39 | r = append(r, entry) 40 | } 41 | } 42 | return 43 | } 44 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/node.js: -------------------------------------------------------------------------------- 1 | var Pubsub = require('../lib/pubsub'); 2 | var AppState = require('../app_state'); 3 | 4 | var BaseView = require('./base'); 5 | 6 | var NodeView = BaseView.extend({ 7 | className: 'node container-fluid', 8 | 9 | template: require('./spinner.hbs'), 10 | 11 | initialize: function() { 12 | BaseView.prototype.initialize.apply(this, arguments); 13 | this.listenTo(AppState, 'change:graph_interval', this.render); 14 | this.model.fetch() 15 | .done(function(data) { 16 | this.template = require('./node.hbs'); 17 | this.render({'message': data['message']}); 18 | }.bind(this)) 19 | .fail(this.handleViewError.bind(this)) 20 | .always(Pubsub.trigger.bind(Pubsub, 'view:ready')); 21 | } 22 | }); 23 | 24 | module.exports = NodeView; 25 | -------------------------------------------------------------------------------- /internal/app/float_array.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "sort" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | type FloatArray []float64 12 | 13 | func (a *FloatArray) Set(param string) error { 14 | for _, s := range strings.Split(param, ",") { 15 | v, err := strconv.ParseFloat(s, 64) 16 | if err != nil { 17 | log.Fatalf("Could not parse: %s", s) 18 | return nil 19 | } 20 | *a = append(*a, v) 21 | } 22 | sort.Sort(*a) 23 | return nil 24 | } 25 | 26 | func (a FloatArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 27 | func (a FloatArray) Less(i, j int) bool { return a[i] > a[j] } 28 | func (a FloatArray) Len() int { return len(a) } 29 | 30 | func (a *FloatArray) String() string { 31 | var s []string 32 | for _, v := range *a { 33 | s = append(s, fmt.Sprintf("%f", v)) 34 | } 35 | return strings.Join(s, ",") 36 | } 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .godeps 2 | ./build 3 | .cover/ 4 | apps/nsqlookupd/nsqlookupd 5 | apps/nsqd/nsqd 6 | apps/nsqadmin/nsqadmin 7 | bench/bench_reader/bench_reader 8 | bench/bench_writer/bench_writer 9 | bench/bench_channels/bench_channels 10 | apps/nsq_to_nsq/nsq_to_nsq 11 | apps/nsq_to_file/nsq_to_file 12 | apps/nsq_pubsub/nsq_pubsub 13 | apps/nsq_to_http/nsq_to_http 14 | apps/nsq_tail/nsq_tail 15 | apps/nsq_stat/nsq_stat 16 | apps/to_nsq/to_nsq 17 | nsqadmin/static/build 18 | dist 19 | _site 20 | _posts 21 | *.dat 22 | 23 | # Go.gitignore 24 | 25 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 26 | *.o 27 | *.a 28 | *.so 29 | 30 | # Folders 31 | _obj 32 | _test 33 | 34 | # Architecture specific extensions/prefixes 35 | *.[568vq] 36 | [568vq].out 37 | 38 | *.cgo1.go 39 | *.cgo2.c 40 | _cgo_defun.c 41 | _cgo_gotypes.go 42 | _cgo_export.* 43 | 44 | _testmain.go 45 | 46 | *.exe 47 | 48 | profile 49 | 50 | # vim stuff 51 | *.sw[op] 52 | -------------------------------------------------------------------------------- /Godeps: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml 2dff11163ee667d51dcc066660925a92ce138deb 2 | github.com/bitly/go-hostpool 58b95b10d6ca26723a7f46017b348653b825a8d6 3 | github.com/nsqio/go-nsq 642a3f9935f12cb3b747294318d730f56f4c34b4 # v1.0.6-alpha 4 | github.com/bitly/go-simplejson 18db6e68d8fd9cbf2e8ebe4c81a78b96fd9bf05a 5 | github.com/bmizerany/perks/quantile 6cb9d9d729303ee2628580d9aec5db968da3a607 6 | github.com/mreiferson/go-options 7ae3226d3e1fa6a0548f73089c72c96c141f3b95 7 | github.com/mreiferson/go-snappystream 028eae7ab5c4c9e2d1cb4c4ca1e53259bbe7e504 # v0.2.3 8 | github.com/bitly/timer_metrics afad1794bb13e2a094720aeb27c088aa64564895 9 | github.com/blang/semver 9bf7bff48b0388cb75991e58c6df7d13e982f1f2 10 | github.com/julienschmidt/httprouter 6aacfd5ab513e34f7e64ea9627ab9670371b34e7 11 | github.com/judwhite/go-svc/svc 63c12402f579f0bdf022653c821a1aa5d7544f01 12 | -------------------------------------------------------------------------------- /apps/to_nsq/README.md: -------------------------------------------------------------------------------- 1 | # to_nsq 2 | 3 | A tool for publishing to an nsq topic with data from `stdin`. 4 | 5 | ## Usage 6 | 7 | ``` 8 | Usage of ./to_nsq: 9 | -delimiter string 10 | character to split input from stdin (default "\n") 11 | -nsqd-tcp-address value 12 | destination nsqd TCP address (may be given multiple times) 13 | -producer-opt value 14 | option to passthrough to nsq.Producer (may be given multiple times, http://godoc.org/github.com/nsqio/go-nsq#Config) 15 | -rate int 16 | Throttle messages to n/second. 0 to disable 17 | -topic string 18 | NSQ topic to publish to 19 | ``` 20 | 21 | ### Examples 22 | 23 | Publish each line of a file: 24 | 25 | ```bash 26 | $ cat source.txt | to_nsq -topic="topic" -nsqd-tcp-address="127.0.0.1:4150" 27 | ``` 28 | 29 | Publish three messages, in one go: 30 | 31 | ```bash 32 | $ echo "one,two,three" | to_nsq -delimiter="," -topic="topic" -nsqd-tcp-address="127.0.0.1:4150" 33 | ``` -------------------------------------------------------------------------------- /nsqadmin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nsqadmin", 3 | "version": "0.3.0", 4 | "description": "operational dashboard for NSQ (http://nsq.io/)", 5 | "repository": { 6 | "type": "git", 7 | "url": "" 8 | }, 9 | "devDependencies": { 10 | "handlebars": "^1.3.0", 11 | "hbsfy": "^1.3.2", 12 | "browserify": "^4.2.3", 13 | "gulp": "^3.9.1", 14 | "gulp-clean": "^0.3.1", 15 | "gulp-notify": "^1.8.0", 16 | "gulp-sass": "^2.2.0", 17 | "gulp-sourcemaps": "^1.6.0", 18 | "gulp-task-listing": "^1.0.0", 19 | "gulp-uglify": "^1.5.3", 20 | "vinyl-source-stream": "^0.1.1", 21 | "vinyl-buffer": "^1.0.0" 22 | }, 23 | "dependencies": { 24 | "backbone": "^1.2.3", 25 | "bootbox": "^4.4.0", 26 | "bootstrap": "^3.3.6", 27 | "jquery": "^2.2.1", 28 | "moment": "^2.11.2", 29 | "underscore": "^1.8.3" 30 | }, 31 | "browserify": { 32 | "transform": [ 33 | "hbsfy" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /nsqd/test/certs/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDW2VvGSYobIaXW+3u8u/1JFNLmiv3/uHQXfRhgp1p9BtaV18/L 3 | RncsmYC2zm9iEGylx9KuiSP5dw2zEoIMRwRSr+hx6zpiiwjYibdB5Q2Jab9C7msy 4 | WpVFQXahIew0axiwb7TilG5W1wW5dYWKymWVx5KnErkwZlc5p/qYKvMZNwIDAQAB 5 | AoGAFTrtQq7Joty0UoYPOBsrcClKndVmO/qyHhASdJqU6n3efngQ5HlWFWYlveqw 6 | PvR4h/ky7GRI1cHZ7LQLfcMWvhahPceD9GEXT76NPZh6ac/Fe/Ao02NXO4ja0ma2 7 | nqIJKrPzm70lpUeEXEcgo9OJ/WH+BcpaKXGC24qj046J6wECQQDt03l4Bjv/G1lG 8 | 4EhGxKiI2rO0ErUaE/GhulWk0C7ElhH4+Mr5D359Y5Ta9M6M3vVy6UYRoNUjVC5V 9 | RuDkLl9hAkEA50RkGfRF+Lpj/YsSDy8DlDs41AKlkQtA9ksb1U0lF0JkY4K0wCiD 10 | ZHqe2kCb0XThUdbEw77eCrVdVE7nufs3lwJBAN1Z15N7kfnFNZm/A+ZCAW6mx10R 11 | kFd+OoZBTJcCYJT97CpjPV8EKcGAnroP4fLBTYevUFT18YpZPmqGdqhJ9OECQBDq 12 | xc/IOJ4bNFlFpQqLS925/0wy7V2Qzy21DNIrlH0BFErbMtGjpQjil2ArvboyrJDJ 13 | /xa6jY+G+M7D/ttx7v0CQG8MaC7WL1bwIv63ZJYEXEuBOQDOUtTiv+gccuv7gezd 14 | o6NnkPKjhU2yi3WEQMa9nFBG+AMhmbtsvLaX9RHX7EI= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/topics.js: -------------------------------------------------------------------------------- 1 | var Pubsub = require('../lib/pubsub'); 2 | var AppState = require('../app_state'); 3 | 4 | var BaseView = require('./base'); 5 | var Topics = require('../collections/topics'); 6 | 7 | var TopicsView = BaseView.extend({ 8 | className: 'topics container-fluid', 9 | 10 | template: require('./spinner.hbs'), 11 | 12 | initialize: function() { 13 | BaseView.prototype.initialize.apply(this, arguments); 14 | this.listenTo(AppState, 'change:graph_interval', this.render); 15 | this.collection = new Topics(); 16 | this.collection.fetch() 17 | .done(function(data) { 18 | this.template = require('./topics.hbs'); 19 | this.render({'message': data['message']}); 20 | }.bind(this)) 21 | .fail(this.handleViewError.bind(this)) 22 | .always(Pubsub.trigger.bind(Pubsub, 'view:ready')); 23 | } 24 | }); 25 | 26 | module.exports = TopicsView; 27 | -------------------------------------------------------------------------------- /nsqlookupd/options.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "time" 7 | ) 8 | 9 | type Options struct { 10 | Verbose bool `flag:"verbose"` 11 | 12 | TCPAddress string `flag:"tcp-address"` 13 | HTTPAddress string `flag:"http-address"` 14 | BroadcastAddress string `flag:"broadcast-address"` 15 | 16 | InactiveProducerTimeout time.Duration `flag:"inactive-producer-timeout"` 17 | TombstoneLifetime time.Duration `flag:"tombstone-lifetime"` 18 | 19 | Logger Logger 20 | } 21 | 22 | func NewOptions() *Options { 23 | hostname, err := os.Hostname() 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | return &Options{ 29 | TCPAddress: "0.0.0.0:4160", 30 | HTTPAddress: "0.0.0.0:4161", 31 | BroadcastAddress: hostname, 32 | 33 | InactiveProducerTimeout: 300 * time.Second, 34 | TombstoneLifetime: 45 * time.Second, 35 | 36 | Logger: log.New(os.Stderr, "[nsqlookupd] ", log.Ldate|log.Ltime|log.Lmicroseconds), 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contrib/nsqadmin.cfg.example: -------------------------------------------------------------------------------- 1 | ## : to listen on for HTTP clients 2 | http_address = "0.0.0.0:4171" 3 | 4 | ## graphite HTTP address 5 | graphite_url = "" 6 | 7 | ## proxy HTTP requests to graphite 8 | proxy_graphite = false 9 | 10 | ## prefix used for keys sent to statsd (%s for host replacement, must match nsqd) 11 | statsd_prefix = "nsq.%s" 12 | 13 | ## format of statsd counter stats 14 | statsd_counter_format = "stats.counters.%s.count" 15 | 16 | ## format of statsd gauge stats 17 | statsd_gauge_format = "stats.gauges.%s" 18 | 19 | ## time interval nsqd is configured to push to statsd (must match nsqd) 20 | statsd_interval = "60s" 21 | 22 | ## HTTP endpoint (fully qualified) to which POST notifications of admin actions will be sent 23 | notification_http_endpoint = "" 24 | 25 | 26 | ## nsqlookupd HTTP addresses 27 | nsqlookupd_http_addresses = [ 28 | "127.0.0.1:4161" 29 | ] 30 | 31 | ## nsqd HTTP addresses (optional) 32 | nsqd_http_addresses = [ 33 | "127.0.0.1:4151" 34 | ] 35 | -------------------------------------------------------------------------------- /internal/http_api/http_server.go: -------------------------------------------------------------------------------- 1 | package http_api 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "net/http" 8 | "strings" 9 | 10 | "github.com/nsqio/nsq/internal/app" 11 | ) 12 | 13 | type logWriter struct { 14 | app.Logger 15 | } 16 | 17 | func (l logWriter) Write(p []byte) (int, error) { 18 | l.Logger.Output(2, string(p)) 19 | return len(p), nil 20 | } 21 | 22 | func Serve(listener net.Listener, handler http.Handler, proto string, l app.Logger) { 23 | l.Output(2, fmt.Sprintf("%s: listening on %s", proto, listener.Addr())) 24 | 25 | server := &http.Server{ 26 | Handler: handler, 27 | ErrorLog: log.New(logWriter{l}, "", 0), 28 | } 29 | err := server.Serve(listener) 30 | // theres no direct way to detect this error because it is not exposed 31 | if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { 32 | l.Output(2, fmt.Sprintf("ERROR: http.Serve() - %s", err)) 33 | } 34 | 35 | l.Output(2, fmt.Sprintf("%s: closing %s", proto, listener.Addr())) 36 | } 37 | -------------------------------------------------------------------------------- /nsqd/test/certs/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,659DE7672771D6C3 4 | 5 | dOStQddwNzG7ynP6yvGchILIZRce1EWRKyTobdeTyrpoUlmQSw6+Z42Wx5Dye5gg 6 | b6HdZSoimv8jjyFk0f3sPqYeuyeAOzcL3biXE+gN6aozwbm5TBB1zPuk4sC6dm38 7 | 2L5VUiJSpTdbYNA8fVS9xj8gNY2OS5zMYcVbnOpPrXwi76225BE3mjlpRFhm05Sb 8 | vcrQPwbBXqQHk/vUQkaPFeT0337GQRVu7SU4ei2xXE7Dr2Zm+bVqzUs3KdbW3s+M 9 | iCN8PauJa/5bMX2AQvDybNiH1hxiKLMfd6DQUPn53zJCCJShSt1G/XkJVHFWIK77 10 | l2l+Wwiykc4Y9FFc+t5pzFmWhS6So1/krZ9eawXq5YSAalPb83DevTHVPwzb52oI 11 | EPihfnP/RDOFuzq4AHWRTwx7yVX1IDsaPBXXpIXJ68/1fB9g1YDOXoeJi8thB3Jz 12 | AKYiU+jlwdnfRlPOrEKN0Moeq8wRjC1a4GwDSaiUQFyHTsMwJ3MeZs5GuUCYaDev 13 | AI+3jig9xJNYdRgD6MdvOFtzZo7fxgEdVWni5ZRFAiuHEpZ2jppl+VjV33enUbmj 14 | d/UIZcMaACLv6EzmlplL+rjW7HnRsCDHU7etDR8y+Uegk9xi4FxidxxvZzDrkUEx 15 | X97wE71mQeoWlYLPsPA+cU414jzqdwNH4JV/gTM6O7WBGp+HaANnVwdT+vH+vH/j 16 | ygIpDPX/ETdgM9b2EXEsWVJ9KVSrUH1QxWlDGQLR8JJLrOrWMEXvuBNw3ae712v+ 17 | W2mnVmCqQ1VO8X5Q0K8vvo8xZLJ+3/B6MpGNLI/unNhVMUtCyZ4f0A== 18 | -----END RSA PRIVATE KEY----- 19 | -------------------------------------------------------------------------------- /internal/protocol/tcp_server.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "runtime" 7 | "strings" 8 | 9 | "github.com/nsqio/nsq/internal/app" 10 | ) 11 | 12 | type TCPHandler interface { 13 | Handle(net.Conn) 14 | } 15 | 16 | func TCPServer(listener net.Listener, handler TCPHandler, l app.Logger) { 17 | l.Output(2, fmt.Sprintf("TCP: listening on %s", listener.Addr())) 18 | 19 | for { 20 | clientConn, err := listener.Accept() 21 | if err != nil { 22 | if nerr, ok := err.(net.Error); ok && nerr.Temporary() { 23 | l.Output(2, fmt.Sprintf("NOTICE: temporary Accept() failure - %s", err)) 24 | runtime.Gosched() 25 | continue 26 | } 27 | // theres no direct way to detect this error because it is not exposed 28 | if !strings.Contains(err.Error(), "use of closed network connection") { 29 | l.Output(2, fmt.Sprintf("ERROR: listener.Accept() - %s", err)) 30 | } 31 | break 32 | } 33 | go handler.Handle(clientConn) 34 | } 35 | 36 | l.Output(2, fmt.Sprintf("TCP: closing %s", listener.Addr())) 37 | } 38 | -------------------------------------------------------------------------------- /nsqd/rename_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package nsqd 4 | 5 | import ( 6 | "syscall" 7 | "unsafe" 8 | ) 9 | 10 | var ( 11 | modkernel32 = syscall.NewLazyDLL("kernel32.dll") 12 | procMoveFileExW = modkernel32.NewProc("MoveFileExW") 13 | ) 14 | 15 | const ( 16 | MOVEFILE_REPLACE_EXISTING = 1 17 | ) 18 | 19 | func moveFileEx(sourceFile, targetFile *uint16, flags uint32) error { 20 | ret, _, err := procMoveFileExW.Call(uintptr(unsafe.Pointer(sourceFile)), uintptr(unsafe.Pointer(targetFile)), uintptr(flags)) 21 | if ret == 0 { 22 | if err != nil { 23 | return err 24 | } 25 | return syscall.EINVAL 26 | } 27 | return nil 28 | } 29 | 30 | func atomicRename(sourceFile, targetFile string) error { 31 | lpReplacedFileName, err := syscall.UTF16PtrFromString(targetFile) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | lpReplacementFileName, err := syscall.UTF16PtrFromString(sourceFile) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | return moveFileEx(lpReplacementFileName, lpReplacedFileName, MOVEFILE_REPLACE_EXISTING) 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in 9 | all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 17 | THE SOFTWARE. 18 | -------------------------------------------------------------------------------- /apps/nsq_to_http/http.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/nsqio/nsq/internal/http_api" 9 | "github.com/nsqio/nsq/internal/version" 10 | ) 11 | 12 | var httpclient *http.Client 13 | var userAgent string 14 | 15 | func init() { 16 | httpclient = &http.Client{Transport: http_api.NewDeadlineTransport(*httpConnectTimeout, *httpRequestTimeout), Timeout: *httpRequestTimeout} 17 | userAgent = fmt.Sprintf("nsq_to_http v%s", version.Binary) 18 | } 19 | 20 | func HTTPGet(endpoint string) (*http.Response, error) { 21 | req, err := http.NewRequest("GET", endpoint, nil) 22 | if err != nil { 23 | return nil, err 24 | } 25 | req.Header.Set("User-Agent", userAgent) 26 | return httpclient.Do(req) 27 | } 28 | 29 | func HTTPPost(endpoint string, body *bytes.Buffer) (*http.Response, error) { 30 | req, err := http.NewRequest("POST", endpoint, body) 31 | if err != nil { 32 | return nil, err 33 | } 34 | req.Header.Set("User-Agent", userAgent) 35 | req.Header.Set("Content-Type", *contentType) 36 | return httpclient.Do(req) 37 | } 38 | -------------------------------------------------------------------------------- /nsqadmin/static/js/models/topic.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | 3 | var AppState = require('../app_state'); 4 | var Backbone = require('backbone'); 5 | 6 | var Topic = Backbone.Model.extend({ 7 | idAttribute: 'name', 8 | 9 | constructor: function Topic() { 10 | Backbone.Model.prototype.constructor.apply(this, arguments); 11 | }, 12 | 13 | url: function() { 14 | return AppState.url('/topics/' + encodeURIComponent(this.get('name'))); 15 | }, 16 | 17 | parse: function(response) { 18 | response['nodes'] = _.map(response['nodes'] || [], function(node) { 19 | var nodeParts = node['node'].split(':'); 20 | var port = nodeParts.pop(); 21 | var address = nodeParts.join(':'); 22 | var hostname = node['hostname']; 23 | node['show_broadcast_address'] = hostname.toLowerCase() !== address.toLowerCase(); 24 | node['hostname_port'] = hostname + ':' + port; 25 | return node; 26 | }); 27 | return response; 28 | } 29 | }); 30 | 31 | module.exports = Topic; 32 | -------------------------------------------------------------------------------- /internal/stringy/template.go: -------------------------------------------------------------------------------- 1 | package stringy 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func Commafy(i interface{}) string { 8 | var n int64 9 | switch i.(type) { 10 | case int: 11 | n = int64(i.(int)) 12 | case int64: 13 | n = i.(int64) 14 | case int32: 15 | n = int64(i.(int32)) 16 | } 17 | if n > 1000 { 18 | r := n % 1000 19 | n = n / 1000 20 | return fmt.Sprintf("%s,%03d", Commafy(n), r) 21 | } 22 | return fmt.Sprintf("%d", n) 23 | } 24 | 25 | func FloatToPercent(i float64) string { 26 | return fmt.Sprintf("%.0f", i*100.0) 27 | } 28 | 29 | func PercSuffix(i float64) string { 30 | switch int(i*100) % 10 { 31 | case 1: 32 | return "st" 33 | case 2: 34 | return "nd" 35 | case 3: 36 | return "rd" 37 | } 38 | return "th" 39 | } 40 | 41 | func NanoSecondToHuman(v float64) string { 42 | var suffix string 43 | switch { 44 | case v > 1000000000: 45 | v /= 1000000000 46 | suffix = "s" 47 | case v > 1000000: 48 | v /= 1000000 49 | suffix = "ms" 50 | case v > 1000: 51 | v /= 1000 52 | suffix = "us" 53 | default: 54 | suffix = "ns" 55 | } 56 | return fmt.Sprintf("%0.1f%s", v, suffix) 57 | } 58 | -------------------------------------------------------------------------------- /nsqd/test/certs/client.req: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIC0TCCAbkCAQAwgYsxCzAJBgNVBAYTAkRFMQwwCgYDVQQIEwNOUlcxDjAMBgNV 3 | BAcTBUVhcnRoMRcwFQYDVQQKEw5SYW5kb20gQ29tcGFueTELMAkGA1UECxMCSVQx 4 | EzARBgNVBAMTCmNsaWVudC5jb20xIzAhBgkqhkiG9w0BCQEWFG1yZWlmZXJzb25A 5 | Z21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuoN3PINU 6 | 2vofUyG52xtGkBchan1RKVG1XttTmZEQ9NGig9QzzbwlhoyWE6Bp/P8IBb378xd9 7 | q/kIhf2gf0aGAOa2oAifFhq+sCuxweubf3z9wnlDZ5q2ro4ud+IDJMS2KcbbhH3Z 8 | TrU4AE7/qUHfrScBY3zVwm6s308QgxcNpb4Jt7ULzGF6ug7UotGGCK7IcKqixkRQ 9 | OEKyQFBuwoxndKHy3FxT0cCB66BU16bNAG7/BhrYNIPlo55MxeUqj7Gs1y/INbc+ 10 | JbVdHOVWW+S35oZ+hS2KPonCQaVOiqfroppPlkoungc1uRKKBg/0sZVUdU3ugC5u 11 | RDdOYDAxozfOPQIDAQABoAAwDQYJKoZIhvcNAQEFBQADggEBAKN40fwQnWTUudVM 12 | mCty/UrHJHNP+mREFybzXPk/KDQ6E2M5ZFuJ4dHr1ZLbdVQEragP5TnatGUtkL/I 13 | l6th5SdI89HTslcRZerg00oMulo9B5sgwcZ7xbZpqudSx1mwf/RNazfskGhouxU2 14 | jy2j6LtqhT1q6bhmXTe53Ys0IGmr7J7Z8f+3bwyQPf2QhZKF537+dbYsm7ZpNeZG 15 | /WrD9cLTDv/5bcQcrnV7mu8u7wPfKsAWuL87O3O1NNPFsTmUHlyNR4NJE1hrQ6Qj 16 | UP0TVyCkzzWO2ACLjP7mJ67h7eihLm5muMITb3hd3iXjEzLOsHgxB3lDzRQvDbnn 17 | gY5Go9U= 18 | -----END CERTIFICATE REQUEST----- 19 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/header.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | var $ = require('jquery'); 3 | 4 | var AppState = require('../app_state'); 5 | 6 | var BaseView = require('./base'); 7 | 8 | var HeaderView = BaseView.extend({ 9 | className: 'header', 10 | 11 | template: require('./header.hbs'), 12 | 13 | events: { 14 | 'click .dropdown-menu li': 'onGraphIntervalClick' 15 | }, 16 | 17 | initialize: function() { 18 | BaseView.prototype.initialize.apply(this, arguments); 19 | this.listenTo(AppState, 'change:graph_interval', this.render); 20 | }, 21 | 22 | getRenderCtx: function() { 23 | return _.extend(BaseView.prototype.getRenderCtx.apply(this, arguments), { 24 | 'graph_intervals': ['1h', '2h', '12h', '24h', '48h', '168h', 'off'], 25 | 'graph_interval': AppState.get('graph_interval') 26 | }); 27 | }, 28 | 29 | onReset: function() { 30 | this.render(); 31 | this.$('.dropdown-toggle').dropdown(); 32 | }, 33 | 34 | onGraphIntervalClick: function(e) { 35 | e.stopPropagation(); 36 | AppState.set('graph_interval', $(e.target).text()); 37 | } 38 | }); 39 | 40 | module.exports = HeaderView; 41 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/nodes.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | 3 | var Pubsub = require('../lib/pubsub'); 4 | var AppState = require('../app_state'); 5 | 6 | var BaseView = require('./base'); 7 | 8 | var Nodes = require('../collections/nodes'); 9 | 10 | var NodesView = BaseView.extend({ 11 | className: 'nodes container-fluid', 12 | 13 | template: require('./spinner.hbs'), 14 | 15 | events: { 16 | 'click .conn-count': 'onClickConnCount' 17 | }, 18 | 19 | initialize: function() { 20 | BaseView.prototype.initialize.apply(this, arguments); 21 | this.listenTo(AppState, 'change:graph_interval', this.render); 22 | this.collection = new Nodes(); 23 | this.collection.fetch() 24 | .done(function(data) { 25 | this.template = require('./nodes.hbs'); 26 | this.render({'message': data['message']}); 27 | }.bind(this)) 28 | .fail(this.handleViewError.bind(this)) 29 | .always(Pubsub.trigger.bind(Pubsub, 'view:ready')); 30 | }, 31 | 32 | onClickConnCount: function(e) { 33 | e.preventDefault(); 34 | $(e.target).next().toggle(); 35 | } 36 | }); 37 | 38 | module.exports = NodesView; 39 | -------------------------------------------------------------------------------- /nsqd/test/certs/client.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDLzCCApigAwIBAgIBAjANBgkqhkiG9w0BAQUFADCBkTELMAkGA1UEBhMCREUx 3 | DDAKBgNVBAgTA05SVzEOMAwGA1UEBxMFRWFydGgxFzAVBgNVBAoTDlJhbmRvbSBD 4 | b21wYW55MQswCQYDVQQLEwJJVDEXMBUGA1UEAxMOd3d3LnJhbmRvbS5jb20xJTAj 5 | BgkqhkiG9w0BCQEWFktyeXB0b0tpbmdzQHJhbmRvbS5jb20wHhcNMTYwNDE1MTU1 6 | NjU3WhcNMTcwNDE1MTU1NjU3WjCBizELMAkGA1UEBhMCREUxDDAKBgNVBAgTA05S 7 | VzEOMAwGA1UEBxMFRWFydGgxFzAVBgNVBAoTDlJhbmRvbSBDb21wYW55MQswCQYD 8 | VQQLEwJJVDETMBEGA1UEAxMKY2xpZW50LmNvbTEjMCEGCSqGSIb3DQEJARYUbXJl 9 | aWZlcnNvbkBnbWFpbC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB 10 | AQC6g3c8g1Ta+h9TIbnbG0aQFyFqfVEpUbVe21OZkRD00aKD1DPNvCWGjJYToGn8 11 | /wgFvfvzF32r+QiF/aB/RoYA5ragCJ8WGr6wK7HB65t/fP3CeUNnmrauji534gMk 12 | xLYpxtuEfdlOtTgATv+pQd+tJwFjfNXCbqzfTxCDFw2lvgm3tQvMYXq6DtSi0YYI 13 | rshwqqLGRFA4QrJAUG7CjGd0ofLcXFPRwIHroFTXps0Abv8GGtg0g+WjnkzF5SqP 14 | sazXL8g1tz4ltV0c5VZb5Lfmhn6FLYo+icJBpU6Kp+uimk+WSi6eBzW5EooGD/Sx 15 | lVR1Te6ALm5EN05gMDGjN849AgMBAAGjFzAVMBMGA1UdJQQMMAoGCCsGAQUFBwMC 16 | MA0GCSqGSIb3DQEBBQUAA4GBAA4cusLWqmXJ8eLG2T1jV2ClAuDtrVbw93zoioOo 17 | 6LJlX+Omjk7TdtqAAQCWz+YNPNglAyz+M5IV3dKUvdfvz2+vzeTadstZicEHHCHt 18 | XXlLIEmxCoRchytMxKl/yuKBjN9GMBL4HLAd9tsvPEqTXx2wjeDBURPKxFIqa/B2 19 | H7iT 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /internal/http_api/req_params.go: -------------------------------------------------------------------------------- 1 | package http_api 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "net/http" 7 | "net/url" 8 | ) 9 | 10 | type ReqParams struct { 11 | url.Values 12 | Body []byte 13 | } 14 | 15 | func NewReqParams(req *http.Request) (*ReqParams, error) { 16 | reqParams, err := url.ParseQuery(req.URL.RawQuery) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | data, err := ioutil.ReadAll(req.Body) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | return &ReqParams{reqParams, data}, nil 27 | } 28 | 29 | func (r *ReqParams) Get(key string) (string, error) { 30 | v, ok := r.Values[key] 31 | if !ok { 32 | return "", errors.New("key not in query params") 33 | } 34 | return v[0], nil 35 | } 36 | 37 | func (r *ReqParams) GetAll(key string) ([]string, error) { 38 | v, ok := r.Values[key] 39 | if !ok { 40 | return nil, errors.New("key not in query params") 41 | } 42 | return v, nil 43 | } 44 | 45 | type PostParams struct { 46 | *http.Request 47 | } 48 | 49 | func (p *PostParams) Get(key string) (string, error) { 50 | if p.Request.Form == nil { 51 | p.Request.ParseMultipartForm(1 << 20) 52 | } 53 | if vs, ok := p.Request.Form[key]; ok { 54 | return vs[0], nil 55 | } 56 | return "", errors.New("key not in post params") 57 | } 58 | -------------------------------------------------------------------------------- /internal/protocol/protocol.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "net" 7 | ) 8 | 9 | // Protocol describes the basic behavior of any protocol in the system 10 | type Protocol interface { 11 | IOLoop(conn net.Conn) error 12 | } 13 | 14 | // SendResponse is a server side utility function to prefix data with a length header 15 | // and write to the supplied Writer 16 | func SendResponse(w io.Writer, data []byte) (int, error) { 17 | err := binary.Write(w, binary.BigEndian, int32(len(data))) 18 | if err != nil { 19 | return 0, err 20 | } 21 | 22 | n, err := w.Write(data) 23 | if err != nil { 24 | return 0, err 25 | } 26 | 27 | return (n + 4), nil 28 | } 29 | 30 | // SendFramedResponse is a server side utility function to prefix data with a length header 31 | // and frame header and write to the supplied Writer 32 | func SendFramedResponse(w io.Writer, frameType int32, data []byte) (int, error) { 33 | beBuf := make([]byte, 4) 34 | size := uint32(len(data)) + 4 35 | 36 | binary.BigEndian.PutUint32(beBuf, size) 37 | n, err := w.Write(beBuf) 38 | if err != nil { 39 | return n, err 40 | } 41 | 42 | binary.BigEndian.PutUint32(beBuf, uint32(frameType)) 43 | n, err = w.Write(beBuf) 44 | if err != nil { 45 | return n + 4, err 46 | } 47 | 48 | n, err = w.Write(data) 49 | return n + 8, err 50 | } 51 | -------------------------------------------------------------------------------- /nsqadmin/static/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | nsqadmin 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /nsqd/test/certs/server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDljCCAv+gAwIBAgIJALn9EspclP5DMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYD 3 | VQQGEwJERTEMMAoGA1UECBMDTlJXMQ4wDAYDVQQHEwVFYXJ0aDEXMBUGA1UEChMO 4 | UmFuZG9tIENvbXBhbnkxCzAJBgNVBAsTAklUMRcwFQYDVQQDEw53d3cucmFuZG9t 5 | LmNvbTEjMCEGCSqGSIb3DQEJARYUbXJlaWZlcnNvbkBnbWFpbC5jb20wHhcNMTYw 6 | NDE1MTU1NjU3WhcNMjYwNDEzMTU1NjU3WjCBjzELMAkGA1UEBhMCREUxDDAKBgNV 7 | BAgTA05SVzEOMAwGA1UEBxMFRWFydGgxFzAVBgNVBAoTDlJhbmRvbSBDb21wYW55 8 | MQswCQYDVQQLEwJJVDEXMBUGA1UEAxMOd3d3LnJhbmRvbS5jb20xIzAhBgkqhkiG 9 | 9w0BCQEWFG1yZWlmZXJzb25AZ21haWwuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GN 10 | ADCBiQKBgQDW2VvGSYobIaXW+3u8u/1JFNLmiv3/uHQXfRhgp1p9BtaV18/LRncs 11 | mYC2zm9iEGylx9KuiSP5dw2zEoIMRwRSr+hx6zpiiwjYibdB5Q2Jab9C7msyWpVF 12 | QXahIew0axiwb7TilG5W1wW5dYWKymWVx5KnErkwZlc5p/qYKvMZNwIDAQABo4H3 13 | MIH0MB0GA1UdDgQWBBQrI9FRyF6SVn8uDaCuZkOH9N8hozCBxAYDVR0jBIG8MIG5 14 | gBQrI9FRyF6SVn8uDaCuZkOH9N8ho6GBlaSBkjCBjzELMAkGA1UEBhMCREUxDDAK 15 | BgNVBAgTA05SVzEOMAwGA1UEBxMFRWFydGgxFzAVBgNVBAoTDlJhbmRvbSBDb21w 16 | YW55MQswCQYDVQQLEwJJVDEXMBUGA1UEAxMOd3d3LnJhbmRvbS5jb20xIzAhBgkq 17 | hkiG9w0BCQEWFG1yZWlmZXJzb25AZ21haWwuY29tggkAuf0SylyU/kMwDAYDVR0T 18 | BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQAvUUVCLhqIzoH7Lx5ajxJZGzfANuTE 19 | 0o5MRKBwJrtajHXmkQQc3aFi2yA9ojdp178vQmQ9SkBeP3jQDotrPjKYAvTl2qOv 20 | zcevsE6Kmg3x/SDHTVRI+jBujB9NrivP1erR+UpePtsDwpMswRnCabPZbNw3TEir 21 | qXktn4vQQhXkGw== 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /nsqd/test/certs/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDnDCCAwWgAwIBAgIJAOZaaR2t9jFiMA0GCSqGSIb3DQEBBQUAMIGRMQswCQYD 3 | VQQGEwJERTEMMAoGA1UECBMDTlJXMQ4wDAYDVQQHEwVFYXJ0aDEXMBUGA1UEChMO 4 | UmFuZG9tIENvbXBhbnkxCzAJBgNVBAsTAklUMRcwFQYDVQQDEw53d3cucmFuZG9t 5 | LmNvbTElMCMGCSqGSIb3DQEJARYWS3J5cHRvS2luZ3NAcmFuZG9tLmNvbTAeFw0x 6 | NjA0MTUxNTU2NTdaFw0xNzA0MTUxNTU2NTdaMIGRMQswCQYDVQQGEwJERTEMMAoG 7 | A1UECBMDTlJXMQ4wDAYDVQQHEwVFYXJ0aDEXMBUGA1UEChMOUmFuZG9tIENvbXBh 8 | bnkxCzAJBgNVBAsTAklUMRcwFQYDVQQDEw53d3cucmFuZG9tLmNvbTElMCMGCSqG 9 | SIb3DQEJARYWS3J5cHRvS2luZ3NAcmFuZG9tLmNvbTCBnzANBgkqhkiG9w0BAQEF 10 | AAOBjQAwgYkCgYEAy5GT6xVaIuYAdFrgp6Vcs9BcLtiFztHf5lLsUSXKw8Y/9PJW 11 | 1dU7yDa4HfoeWGgQV5URFauUzKKKWVUWBUqlF+p1+pV3Gbqr9gxiFgNieLJqabHJ 12 | v02QEblLJHM7rdy6H5Xe2lIcNDokwsbJzTPEUZSmQDkMvDiIpKjZ46DmVQcCAwEA 13 | AaOB+TCB9jAdBgNVHQ4EFgQUtaq6IWu7GGnIlMb0szE+oTETTxwwgcYGA1UdIwSB 14 | vjCBu4AUtaq6IWu7GGnIlMb0szE+oTETTxyhgZekgZQwgZExCzAJBgNVBAYTAkRF 15 | MQwwCgYDVQQIEwNOUlcxDjAMBgNVBAcTBUVhcnRoMRcwFQYDVQQKEw5SYW5kb20g 16 | Q29tcGFueTELMAkGA1UECxMCSVQxFzAVBgNVBAMTDnd3dy5yYW5kb20uY29tMSUw 17 | IwYJKoZIhvcNAQkBFhZLcnlwdG9LaW5nc0ByYW5kb20uY29tggkA5lppHa32MWIw 18 | DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQBo/EoOKvrQS1Ad1Oj6os+v 19 | 8FEBJPS/24feAmx7FurAHgRCW4LOgK9ImJSfF9BwVpuT//w8mDOUQrYdx8cyLE/L 20 | bytS0PM0U+P0tuo6Gm4pIFCu4aL/Q/RxYhzg75G1v4dtS2swdk2Y2od91DlTenJb 21 | W14CSH47cx6fhK50ZZDOWQ== 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /contrib/nsq.spec: -------------------------------------------------------------------------------- 1 | %define name nsq 2 | %define version 0.3.8 3 | %define release 1 4 | %define path usr/local 5 | %define group Database/Applications 6 | %define __os_install_post %{nil} 7 | 8 | Summary: nsq 9 | Name: %{name} 10 | Version: %{version} 11 | Release: %{release} 12 | Group: %{group} 13 | Packager: Matt Reiferson 14 | License: Apache 15 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release} 16 | AutoReqProv: no 17 | # we just assume you have go installed. You may or may not have an RPM to depend on. 18 | # BuildRequires: go 19 | 20 | %description 21 | NSQ - A realtime distributed messaging platform 22 | https://github.com/nsqio/nsq 23 | 24 | %prep 25 | mkdir -p $RPM_BUILD_DIR/%{name}-%{version}-%{release} 26 | cd $RPM_BUILD_DIR/%{name}-%{version}-%{release} 27 | git clone git@github.com:nsqio/nsq.git 28 | 29 | %build 30 | cd $RPM_BUILD_DIR/%{name}-%{version}-%{release}/nsq 31 | make PREFIX=/%{path} 32 | 33 | %install 34 | export DONT_STRIP=1 35 | rm -rf $RPM_BUILD_ROOT 36 | cd $RPM_BUILD_DIR/%{name}-%{version}-%{release}/nsq 37 | make PREFIX=/${path} DESTDIR=$RPM_BUILD_ROOT install 38 | 39 | %files 40 | /%{path}/bin/nsqadmin 41 | /%{path}/bin/nsqd 42 | /%{path}/bin/nsqlookupd 43 | /%{path}/bin/nsq_pubsub 44 | /%{path}/bin/nsq_to_file 45 | /%{path}/bin/nsq_to_http 46 | /%{path}/bin/nsq_to_nsq 47 | /%{path}/bin/nsq_tail 48 | /%{path}/bin/nsq_stat 49 | /%{path}/bin/to_nsq 50 | -------------------------------------------------------------------------------- /internal/statsd/client.go: -------------------------------------------------------------------------------- 1 | package statsd 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "time" 8 | ) 9 | 10 | type Client struct { 11 | conn net.Conn 12 | addr string 13 | prefix string 14 | } 15 | 16 | func NewClient(addr string, prefix string) *Client { 17 | return &Client{ 18 | addr: addr, 19 | prefix: prefix, 20 | } 21 | } 22 | 23 | func (c *Client) String() string { 24 | return c.addr 25 | } 26 | 27 | func (c *Client) CreateSocket() error { 28 | conn, err := net.DialTimeout("udp", c.addr, time.Second) 29 | if err != nil { 30 | return err 31 | } 32 | c.conn = conn 33 | return nil 34 | } 35 | 36 | func (c *Client) Close() error { 37 | return c.conn.Close() 38 | } 39 | 40 | func (c *Client) Incr(stat string, count int64) error { 41 | return c.send(stat, "%d|c", count) 42 | } 43 | 44 | func (c *Client) Decr(stat string, count int64) error { 45 | return c.send(stat, "%d|c", -count) 46 | } 47 | 48 | func (c *Client) Timing(stat string, delta int64) error { 49 | return c.send(stat, "%d|ms", delta) 50 | } 51 | 52 | func (c *Client) Gauge(stat string, value int64) error { 53 | return c.send(stat, "%d|g", value) 54 | } 55 | 56 | func (c *Client) send(stat string, format string, value int64) error { 57 | if c.conn == nil { 58 | return errors.New("not connected") 59 | } 60 | format = fmt.Sprintf("%s%s:%s", c.prefix, stat, format) 61 | _, err := fmt.Fprintf(c.conn, format, value) 62 | return err 63 | } 64 | -------------------------------------------------------------------------------- /nsqd/tcp.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | import ( 4 | "io" 5 | "net" 6 | 7 | "github.com/nsqio/nsq/internal/protocol" 8 | ) 9 | 10 | type tcpServer struct { 11 | ctx *context 12 | } 13 | 14 | func (p *tcpServer) Handle(clientConn net.Conn) { 15 | p.ctx.nsqd.logf("TCP: new client(%s)", clientConn.RemoteAddr()) 16 | 17 | // The client should initialize itself by sending a 4 byte sequence indicating 18 | // the version of the protocol that it intends to communicate, this will allow us 19 | // to gracefully upgrade the protocol away from text/line oriented to whatever... 20 | buf := make([]byte, 4) 21 | _, err := io.ReadFull(clientConn, buf) 22 | if err != nil { 23 | p.ctx.nsqd.logf("ERROR: failed to read protocol version - %s", err) 24 | return 25 | } 26 | protocolMagic := string(buf) 27 | 28 | p.ctx.nsqd.logf("CLIENT(%s): desired protocol magic '%s'", 29 | clientConn.RemoteAddr(), protocolMagic) 30 | 31 | var prot protocol.Protocol 32 | switch protocolMagic { 33 | case " V2": 34 | prot = &protocolV2{ctx: p.ctx} 35 | default: 36 | protocol.SendFramedResponse(clientConn, frameTypeError, []byte("E_BAD_PROTOCOL")) 37 | clientConn.Close() 38 | p.ctx.nsqd.logf("ERROR: client(%s) bad protocol magic '%s'", 39 | clientConn.RemoteAddr(), protocolMagic) 40 | return 41 | } 42 | 43 | err = prot.IOLoop(clientConn) 44 | if err != nil { 45 | p.ctx.nsqd.logf("ERROR: client(%s) - %s", clientConn.RemoteAddr(), err) 46 | return 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | readonly messageSize="${1:-200}" 3 | readonly batchSize="${2:-200}" 4 | readonly memQueueSize="${3:-1000000}" 5 | readonly dataPath="${4:-}" 6 | set -e 7 | set -u 8 | 9 | echo "# using --mem-queue-size=$memQueueSize --data-path=$dataPath --size=$messageSize --batch-size=$batchSize" 10 | echo "# compiling/running nsqd" 11 | pushd apps/nsqd >/dev/null 12 | go build 13 | rm -f *.dat 14 | ./nsqd --mem-queue-size=$memQueueSize --data-path=$dataPath >/dev/null 2>&1 & 15 | nsqd_pid=$! 16 | popd >/dev/null 17 | 18 | cleanup() { 19 | kill -9 $nsqd_pid 20 | rm -f nsqd/*.dat 21 | } 22 | trap cleanup INT TERM EXIT 23 | 24 | sleep 0.3 25 | echo "# creating topic/channel" 26 | curl --silent 'http://127.0.0.1:4151/create_topic?topic=sub_bench' >/dev/null 2>&1 27 | curl --silent 'http://127.0.0.1:4151/create_channel?topic=sub_bench&channel=ch' >/dev/null 2>&1 28 | 29 | echo "# compiling bench_reader/bench_writer" 30 | pushd bench >/dev/null 31 | for app in bench_reader bench_writer; do 32 | pushd $app >/dev/null 33 | go build 34 | popd >/dev/null 35 | done 36 | popd >/dev/null 37 | 38 | echo -n "PUB: " 39 | bench/bench_writer/bench_writer --size=$messageSize --batch-size=$batchSize 2>&1 40 | 41 | curl -s -o cpu.pprof http://127.0.0.1:4151/debug/pprof/profile & 42 | pprof_pid=$! 43 | 44 | echo -n "SUB: " 45 | bench/bench_reader/bench_reader --size=$messageSize --channel=ch 2>&1 46 | 47 | echo "waiting for pprof..." 48 | wait $pprof_pid 49 | -------------------------------------------------------------------------------- /nsqlookupd/tcp.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd 2 | 3 | import ( 4 | "io" 5 | "net" 6 | 7 | "github.com/nsqio/nsq/internal/protocol" 8 | ) 9 | 10 | type tcpServer struct { 11 | ctx *Context 12 | } 13 | 14 | func (p *tcpServer) Handle(clientConn net.Conn) { 15 | p.ctx.nsqlookupd.logf("TCP: new client(%s)", clientConn.RemoteAddr()) 16 | 17 | // The client should initialize itself by sending a 4 byte sequence indicating 18 | // the version of the protocol that it intends to communicate, this will allow us 19 | // to gracefully upgrade the protocol away from text/line oriented to whatever... 20 | buf := make([]byte, 4) 21 | _, err := io.ReadFull(clientConn, buf) 22 | if err != nil { 23 | p.ctx.nsqlookupd.logf("ERROR: failed to read protocol version - %s", err) 24 | return 25 | } 26 | protocolMagic := string(buf) 27 | 28 | p.ctx.nsqlookupd.logf("CLIENT(%s): desired protocol magic '%s'", 29 | clientConn.RemoteAddr(), protocolMagic) 30 | 31 | var prot protocol.Protocol 32 | switch protocolMagic { 33 | case " V1": 34 | prot = &LookupProtocolV1{ctx: p.ctx} 35 | default: 36 | protocol.SendResponse(clientConn, []byte("E_BAD_PROTOCOL")) 37 | clientConn.Close() 38 | p.ctx.nsqlookupd.logf("ERROR: client(%s) bad protocol magic '%s'", 39 | clientConn.RemoteAddr(), protocolMagic) 40 | return 41 | } 42 | 43 | err = prot.IOLoop(clientConn) 44 | if err != nil { 45 | p.ctx.nsqlookupd.logf("ERROR: client(%s) - %s", clientConn.RemoteAddr(), err) 46 | return 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /nsqadmin/static/js/app_state.js: -------------------------------------------------------------------------------- 1 | var Backbone = require('backbone'); 2 | var _ = require('underscore'); 3 | 4 | var AppState = Backbone.Model.extend({ 5 | defaults: function() { 6 | return { 7 | 'VERSION': VERSION, 8 | 'GRAPHITE_URL': GRAPHITE_URL, 9 | 'GRAPH_ENABLED': GRAPH_ENABLED, 10 | 'STATSD_INTERVAL': STATSD_INTERVAL, 11 | 'USE_STATSD_PREFIXES': USE_STATSD_PREFIXES, 12 | 'STATSD_COUNTER_FORMAT': STATSD_COUNTER_FORMAT, 13 | 'STATSD_GAUGE_FORMAT': STATSD_GAUGE_FORMAT, 14 | 'STATSD_PREFIX': STATSD_PREFIX, 15 | 'NSQLOOKUPD': NSQLOOKUPD, 16 | 'graph_interval': '2h' 17 | }; 18 | }, 19 | 20 | initialize: function() { 21 | this.on('change:graph_interval', function(model, v) { 22 | localStorage.setItem('graph_interval', v); 23 | }); 24 | 25 | var qp = _.object(_.compact(_.map(window.location.search.slice(1).split('&'), 26 | function(item) { if (item) { return item.split('='); } }))); 27 | 28 | var def = this.get('GRAPH_ENABLED') ? '2h' : 'off'; 29 | var interval = qp['t'] || localStorage.getItem('graph_interval') || def; 30 | this.set('graph_interval', interval); 31 | }, 32 | 33 | url: function(url) { 34 | return '/api' + url; 35 | } 36 | }); 37 | 38 | var appState = new AppState(); 39 | 40 | window.AppState = appState; 41 | 42 | module.exports = appState; 43 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/topics.hbs: -------------------------------------------------------------------------------- 1 | {{> warning}} 2 | {{> error}} 3 | 4 |
5 |
6 |

Topics

7 |
8 |
9 | 10 |
11 |
12 | {{#if collection.length}} 13 | 14 | 15 | 16 | {{#if graph_active}}{{/if}} 17 | {{#if graph_active}}{{/if}} 18 | {{#if graph_active}}{{/if}} 19 | 20 | {{#each collection}} 21 | 22 | 23 | {{#if ../graph_active}}{{/if}} 24 | {{#if ../graph_active}}{{/if}} 25 | {{#if ../graph_active}}{{/if}} 26 | 27 | {{/each}} 28 |
TopicDepthMessagesRate
{{name}}
29 | {{else}} 30 |

Notice

No Topics Found
31 | {{/if}} 32 |
33 |
34 | -------------------------------------------------------------------------------- /internal/protocol/errors.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | type ChildErr interface { 4 | Parent() error 5 | } 6 | 7 | // ClientErr provides a way for NSQ daemons to log a human reabable 8 | // error string and return a machine readable string to the client. 9 | // 10 | // see docs/protocol.md for error codes by command 11 | type ClientErr struct { 12 | ParentErr error 13 | Code string 14 | Desc string 15 | } 16 | 17 | // Error returns the machine readable form 18 | func (e *ClientErr) Error() string { 19 | return e.Code + " " + e.Desc 20 | } 21 | 22 | // Parent returns the parent error 23 | func (e *ClientErr) Parent() error { 24 | return e.ParentErr 25 | } 26 | 27 | // NewClientErr creates a ClientErr with the supplied human and machine readable strings 28 | func NewClientErr(parent error, code string, description string) *ClientErr { 29 | return &ClientErr{parent, code, description} 30 | } 31 | 32 | type FatalClientErr struct { 33 | ParentErr error 34 | Code string 35 | Desc string 36 | } 37 | 38 | // Error returns the machine readable form 39 | func (e *FatalClientErr) Error() string { 40 | return e.Code + " " + e.Desc 41 | } 42 | 43 | // Parent returns the parent error 44 | func (e *FatalClientErr) Parent() error { 45 | return e.ParentErr 46 | } 47 | 48 | // NewClientErr creates a ClientErr with the supplied human and machine readable strings 49 | func NewFatalClientErr(parent error, code string, description string) *FatalClientErr { 50 | return &FatalClientErr{parent, code, description} 51 | } 52 | -------------------------------------------------------------------------------- /coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Generate test coverage statistics for Go packages. 3 | # 4 | # Works around the fact that `go test -coverprofile` currently does not work 5 | # with multiple packages, see https://code.google.com/p/go/issues/detail?id=6909 6 | # 7 | # Usage: coverage.sh [--html|--coveralls] 8 | # 9 | # --html Additionally create HTML report 10 | # --coveralls Push coverage statistics to coveralls.io 11 | # 12 | 13 | set -e 14 | 15 | workdir=.cover 16 | profile="$workdir/cover.out" 17 | mode=count 18 | 19 | generate_cover_data() { 20 | rm -rf "$workdir" 21 | mkdir "$workdir" 22 | 23 | for pkg in "$@"; do 24 | f="$workdir/$(echo $pkg | tr / -).cover" 25 | go test -covermode="$mode" -coverprofile="$f" "$pkg" 26 | done 27 | 28 | echo "mode: $mode" >"$profile" 29 | grep -h -v "^mode:" "$workdir"/*.cover >>"$profile" 30 | } 31 | 32 | show_html_report() { 33 | go tool cover -html="$profile" -o="$workdir"/coverage.html 34 | } 35 | 36 | show_csv_report() { 37 | go tool cover -func="$profile" -o="$workdir"/coverage.csv 38 | } 39 | 40 | push_to_coveralls() { 41 | echo "Pushing coverage statistics to coveralls.io" 42 | $HOME/gopath/bin/goveralls -coverprofile="$profile" -service=travis-ci -ignore="nsqadmin/bindata.go" 43 | } 44 | 45 | generate_cover_data $(go list ./...) 46 | show_csv_report 47 | 48 | case "$1" in 49 | "") 50 | ;; 51 | --html) 52 | show_html_report ;; 53 | --coveralls) 54 | push_to_coveralls ;; 55 | *) 56 | echo >&2 "error: invalid option: $1"; exit 1 ;; 57 | esac 58 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX=/usr/local 2 | DESTDIR= 3 | GOFLAGS= 4 | BINDIR=${PREFIX}/bin 5 | 6 | BLDDIR = build 7 | EXT= 8 | ifeq (${GOOS},windows) 9 | EXT=.exe 10 | endif 11 | 12 | APPS = nsqd nsqlookupd nsqadmin nsq_pubsub nsq_to_nsq nsq_to_file nsq_to_http nsq_tail nsq_stat to_nsq 13 | all: $(APPS) 14 | 15 | $(BLDDIR)/nsqd: $(wildcard apps/nsqd/*.go nsqd/*.go nsq/*.go internal/*/*.go) 16 | $(BLDDIR)/nsqlookupd: $(wildcard apps/nsqlookupd/*.go nsqlookupd/*.go nsq/*.go internal/*/*.go) 17 | $(BLDDIR)/nsqadmin: $(wildcard apps/nsqadmin/*.go nsqadmin/*.go nsqadmin/templates/*.go internal/*/*.go) 18 | $(BLDDIR)/nsq_pubsub: $(wildcard apps/nsq_pubsub/*.go nsq/*.go internal/*/*.go) 19 | $(BLDDIR)/nsq_to_nsq: $(wildcard apps/nsq_to_nsq/*.go nsq/*.go internal/*/*.go) 20 | $(BLDDIR)/nsq_to_file: $(wildcard apps/nsq_to_file/*.go nsq/*.go internal/*/*.go) 21 | $(BLDDIR)/nsq_to_http: $(wildcard apps/nsq_to_http/*.go nsq/*.go internal/*/*.go) 22 | $(BLDDIR)/nsq_tail: $(wildcard apps/nsq_tail/*.go nsq/*.go internal/*/*.go) 23 | $(BLDDIR)/nsq_stat: $(wildcard apps/nsq_stat/*.go internal/*/*.go) 24 | $(BLDDIR)/to_nsq: $(wildcard apps/to_nsq/*.go internal/*/*.go) 25 | 26 | $(BLDDIR)/%: 27 | @mkdir -p $(dir $@) 28 | go build ${GOFLAGS} -o $@ ./apps/$* 29 | 30 | $(APPS): %: $(BLDDIR)/% 31 | 32 | clean: 33 | rm -fr $(BLDDIR) 34 | 35 | .PHONY: install clean all 36 | .PHONY: $(APPS) 37 | 38 | install: $(APPS) 39 | install -m 755 -d ${DESTDIR}${BINDIR} 40 | for APP in $^ ; do install -m 755 ${BLDDIR}/$$APP ${DESTDIR}${BINDIR}/$$APP${EXT} ; done 41 | -------------------------------------------------------------------------------- /nsqd/test/cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # call this script with an email address (valid or not). 3 | # like: 4 | # ./cert.sh foo@foo.com 5 | # Found: https://gist.github.com/ncw/9253562#file-makecert-sh 6 | 7 | if [ "$1" == "" ]; then 8 | echo "Need email as argument" 9 | exit 1 10 | fi 11 | 12 | EMAIL=$1 13 | 14 | rm -rf tmp 15 | mkdir tmp 16 | cd tmp 17 | 18 | echo "make CA" 19 | PRIVKEY="test" 20 | openssl req -new -x509 -days 365 -keyout ca.key -out ca.pem -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=www.random.com/emailAddress=KryptoKings@random.com" -passout pass:$PRIVKEY 21 | 22 | echo "make server cert" 23 | openssl req -new -nodes -x509 -out server.pem -keyout server.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=www.random.com/emailAddress=${EMAIL}" 24 | 25 | echo "make client cert" 26 | #openssl req -new -nodes -x509 -out client.pem -keyout client.key 27 | #-days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random 28 | #Company/OU=IT/CN=www.random.com/emailAddress=${EMAIL}" 29 | 30 | openssl genrsa -out client.key 2048 31 | echo "00" > ca.srl 32 | openssl req -sha1 -key client.key -new -out client.req -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=client.com/emailAddress=${EMAIL}" 33 | # Adding -addtrust clientAuth makes certificates Go can't read 34 | openssl x509 -req -days 365 -in client.req -CA ca.pem -CAkey ca.key -passin pass:$PRIVKEY -out client.pem # -addtrust clientAuth 35 | 36 | openssl x509 -extfile ../openssl.conf -extensions ssl_client -req -days 365 -in client.req -CA ca.pem -CAkey ca.key -passin pass:$PRIVKEY -out client.pem 37 | 38 | cd .. 39 | mv tmp/* certs 40 | rm -rf tmp 41 | -------------------------------------------------------------------------------- /internal/pqueue/pqueue.go: -------------------------------------------------------------------------------- 1 | package pqueue 2 | 3 | import ( 4 | "container/heap" 5 | ) 6 | 7 | type Item struct { 8 | Value interface{} 9 | Priority int64 10 | Index int 11 | } 12 | 13 | // this is a priority queue as implemented by a min heap 14 | // ie. the 0th element is the *lowest* value 15 | type PriorityQueue []*Item 16 | 17 | func New(capacity int) PriorityQueue { 18 | return make(PriorityQueue, 0, capacity) 19 | } 20 | 21 | func (pq PriorityQueue) Len() int { 22 | return len(pq) 23 | } 24 | 25 | func (pq PriorityQueue) Less(i, j int) bool { 26 | return pq[i].Priority < pq[j].Priority 27 | } 28 | 29 | func (pq PriorityQueue) Swap(i, j int) { 30 | pq[i], pq[j] = pq[j], pq[i] 31 | pq[i].Index = i 32 | pq[j].Index = j 33 | } 34 | 35 | func (pq *PriorityQueue) Push(x interface{}) { 36 | n := len(*pq) 37 | c := cap(*pq) 38 | if n+1 > c { 39 | npq := make(PriorityQueue, n, c*2) 40 | copy(npq, *pq) 41 | *pq = npq 42 | } 43 | *pq = (*pq)[0 : n+1] 44 | item := x.(*Item) 45 | item.Index = n 46 | (*pq)[n] = item 47 | } 48 | 49 | func (pq *PriorityQueue) Pop() interface{} { 50 | n := len(*pq) 51 | c := cap(*pq) 52 | if n < (c/2) && c > 25 { 53 | npq := make(PriorityQueue, n, c/2) 54 | copy(npq, *pq) 55 | *pq = npq 56 | } 57 | item := (*pq)[n-1] 58 | item.Index = -1 59 | *pq = (*pq)[0 : n-1] 60 | return item 61 | } 62 | 63 | func (pq *PriorityQueue) PeekAndShift(max int64) (*Item, int64) { 64 | if pq.Len() == 0 { 65 | return nil, 0 66 | } 67 | 68 | item := (*pq)[0] 69 | if item.Priority > max { 70 | return nil, item.Priority - max 71 | } 72 | heap.Remove(pq, 0) 73 | 74 | return item, 0 75 | } 76 | -------------------------------------------------------------------------------- /internal/test/assertions.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "path/filepath" 5 | "reflect" 6 | "runtime" 7 | "testing" 8 | ) 9 | 10 | func Equal(t *testing.T, expected, actual interface{}) { 11 | if !reflect.DeepEqual(expected, actual) { 12 | _, file, line, _ := runtime.Caller(1) 13 | t.Logf("\033[31m%s:%d:\n\n\t %#v (expected)\n\n\t!= %#v (actual)\033[39m\n\n", 14 | filepath.Base(file), line, expected, actual) 15 | t.FailNow() 16 | } 17 | } 18 | 19 | func NotEqual(t *testing.T, expected, actual interface{}) { 20 | if reflect.DeepEqual(expected, actual) { 21 | _, file, line, _ := runtime.Caller(1) 22 | t.Logf("\033[31m%s:%d:\n\n\tnexp: %#v\n\n\tgot: %#v\033[39m\n\n", 23 | filepath.Base(file), line, expected, actual) 24 | t.FailNow() 25 | } 26 | } 27 | 28 | func Nil(t *testing.T, object interface{}) { 29 | if !isNil(object) { 30 | _, file, line, _ := runtime.Caller(1) 31 | t.Logf("\033[31m%s:%d:\n\n\t (expected)\n\n\t!= %#v (actual)\033[39m\n\n", 32 | filepath.Base(file), line, object) 33 | t.FailNow() 34 | } 35 | } 36 | 37 | func NotNil(t *testing.T, object interface{}) { 38 | if isNil(object) { 39 | _, file, line, _ := runtime.Caller(1) 40 | t.Logf("\033[31m%s:%d:\n\n\tExpected value not to be \033[39m\n\n", 41 | filepath.Base(file), line, object) 42 | t.FailNow() 43 | } 44 | } 45 | 46 | func isNil(object interface{}) bool { 47 | if object == nil { 48 | return true 49 | } 50 | 51 | value := reflect.ValueOf(object) 52 | kind := value.Kind() 53 | if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() { 54 | return true 55 | } 56 | 57 | return false 58 | } 59 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for your interest in contributing to NSQ! 4 | 5 | ## Code of Conduct 6 | 7 | Help us keep NSQ open and inclusive. Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md). 8 | 9 | ## Getting Started 10 | 11 | * make sure you have a [GitHub account](https://github.com/signup/free) 12 | * submit a ticket for your issue, assuming one does not already exist 13 | * clearly describe the issue including steps to reproduce when it is a bug 14 | * identify specific versions of the binaries and client libraries 15 | * fork the repository on GitHub 16 | 17 | ## Making Changes 18 | 19 | * create a branch from where you want to base your work 20 | * we typically name branches according to the following format: `helpful_name_` 21 | * make commits of logical units 22 | * make sure your commit messages are in a clear and readable format, example: 23 | 24 | ``` 25 | nsqd: fixed bug in protocol_v2 26 | 27 | * update the message pump to properly account for RDYness 28 | * cleanup variable names 29 | * ... 30 | ``` 31 | 32 | * if you're fixing a bug or adding functionality it probably makes sense to write a test 33 | * make sure to run `fmt.sh` and `test.sh` in the root of the repo to ensure that your code is 34 | properly formatted and that tests pass (NOTE: we integrate Travis with GitHub for continuous 35 | integration) 36 | 37 | ## Submitting Changes 38 | 39 | * push your changes to your branch in your fork of the repository 40 | * submit a pull request against nsqio's repository 41 | * comment in the pull request when you're ready for the changes to be reviewed: `"ready for review"` 42 | -------------------------------------------------------------------------------- /nsqadmin/static/js/router.js: -------------------------------------------------------------------------------- 1 | var Backbone = require('backbone'); 2 | 3 | var Pubsub = require('./lib/pubsub'); 4 | 5 | 6 | var Router = Backbone.Router.extend({ 7 | routes: { 8 | '': 'topics', 9 | 'topics/(:topic)(/:channel)': 'topic', 10 | 'lookup': 'lookup', 11 | 'nodes(/:node)': 'nodes', 12 | 'counter': 'counter' 13 | }, 14 | 15 | defaultRoute: 'topics', 16 | 17 | initialize: function() { 18 | this.currentRoute = this.defaultRoute; 19 | this.listenTo(this, 'route', function(route, params) { 20 | this.currentRoute = route || this.defaultRoute; 21 | // console.log('Route: %o; params: %o', route, params); 22 | }); 23 | }, 24 | 25 | start: function() { 26 | Backbone.history.start({ 27 | 'pushState': true 28 | }); 29 | }, 30 | 31 | topics: function() { 32 | Pubsub.trigger('topics:show'); 33 | }, 34 | 35 | topic: function(topic, channel) { 36 | if (channel !== null) { 37 | Pubsub.trigger('channel:show', topic, channel); 38 | return; 39 | } 40 | Pubsub.trigger('topic:show', topic); 41 | }, 42 | 43 | lookup: function() { 44 | Pubsub.trigger('lookup:show'); 45 | }, 46 | 47 | nodes: function(node) { 48 | if (node !== null) { 49 | Pubsub.trigger('node:show', node); 50 | return; 51 | } 52 | Pubsub.trigger('nodes:show'); 53 | }, 54 | 55 | counter: function() { 56 | Pubsub.trigger('counter:show'); 57 | } 58 | }); 59 | 60 | 61 | module.exports = new Router(); 62 | -------------------------------------------------------------------------------- /nsqlookupd/lookup_protocol_v1_test.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | "time" 7 | 8 | "github.com/nsqio/nsq/internal/protocol" 9 | "github.com/nsqio/nsq/internal/test" 10 | ) 11 | 12 | func TestIOLoopReturnsClientErrWhenSendFails(t *testing.T) { 13 | fakeConn := test.NewFakeNetConn() 14 | fakeConn.WriteFunc = func(b []byte) (int, error) { 15 | return 0, errors.New("write error") 16 | } 17 | 18 | testIOLoopReturnsClientErr(t, fakeConn) 19 | } 20 | 21 | func TestIOLoopReturnsClientErrWhenSendSucceeds(t *testing.T) { 22 | fakeConn := test.NewFakeNetConn() 23 | fakeConn.WriteFunc = func(b []byte) (int, error) { 24 | return len(b), nil 25 | } 26 | 27 | testIOLoopReturnsClientErr(t, fakeConn) 28 | } 29 | 30 | func testIOLoopReturnsClientErr(t *testing.T, fakeConn test.FakeNetConn) { 31 | fakeConn.ReadFunc = func(b []byte) (int, error) { 32 | return copy(b, []byte("INVALID_COMMAND\n")), nil 33 | } 34 | 35 | opts := NewOptions() 36 | opts.Logger = test.NewTestLogger(t) 37 | opts.Verbose = true 38 | 39 | prot := &LookupProtocolV1{ctx: &Context{nsqlookupd: New(opts)}} 40 | 41 | errChan := make(chan error) 42 | testIOLoop := func() { 43 | errChan <- prot.IOLoop(fakeConn) 44 | defer prot.ctx.nsqlookupd.Exit() 45 | } 46 | go testIOLoop() 47 | 48 | var err error 49 | var timeout bool 50 | 51 | select { 52 | case err = <-errChan: 53 | case <-time.After(2 * time.Second): 54 | timeout = true 55 | } 56 | 57 | test.Equal(t, false, timeout) 58 | 59 | test.NotNil(t, err) 60 | test.Equal(t, "E_INVALID invalid command INVALID_COMMAND", err.Error()) 61 | test.NotNil(t, err.(*protocol.FatalClientErr)) 62 | } 63 | -------------------------------------------------------------------------------- /nsqd/test/certs/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEbjCCA1agAwIBAgIJAK6x7y6AwBmLMA0GCSqGSIb3DQEBBQUAMIGAMQswCQYD 3 | VQQGEwJVUzERMA8GA1UECBMITmV3IFlvcmsxFjAUBgNVBAcTDU5ldyBZb3JrIENp 4 | dHkxDDAKBgNVBAoTA05TUTETMBEGA1UEAxMKdGVzdC5sb2NhbDEjMCEGCSqGSIb3 5 | DQEJARYUbXJlaWZlcnNvbkBnbWFpbC5jb20wHhcNMTMwNjI4MDA0MzQ4WhcNMTYw 6 | NDE3MDA0MzQ4WjCBgDELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE5ldyBZb3JrMRYw 7 | FAYDVQQHEw1OZXcgWW9yayBDaXR5MQwwCgYDVQQKEwNOU1ExEzARBgNVBAMTCnRl 8 | c3QubG9jYWwxIzAhBgkqhkiG9w0BCQEWFG1yZWlmZXJzb25AZ21haWwuY29tMIIB 9 | IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnX0KB+svwy+yHU2qggz/EaGg 10 | craKShagKo+9M9y5HLM852ngk5c+t+tJJbx3N954Wr1FXBuGIv1ltU05rU4zhvBS 11 | 25tVP1UIEnT5pBt2TeetLkl199Y7fxh1hKmnwJMG3fy3VZdNXEndBombXMmtXpQY 12 | shuEJHKeUNDbQKz5X+GjEdkTPO/HY/VMHsxS23pbSimQozMg3hvLIdgv0aS3QECz 13 | ydZBgTPThy3uDtHIuCpxCwXd/vDF68ATlYgo3h3lh2vxNwM/pjklIUhzMh4XaKQF 14 | 7m3/0KbtUcXfy0QHueeuMr11E9MAFNyRN4xf9Fk1yB97KJ3PJBTC5WD/m1nW+QID 15 | AQABo4HoMIHlMB0GA1UdDgQWBBR3HMBws4lmYYSIgwoZsfW+bbgaMjCBtQYDVR0j 16 | BIGtMIGqgBR3HMBws4lmYYSIgwoZsfW+bbgaMqGBhqSBgzCBgDELMAkGA1UEBhMC 17 | VVMxETAPBgNVBAgTCE5ldyBZb3JrMRYwFAYDVQQHEw1OZXcgWW9yayBDaXR5MQww 18 | CgYDVQQKEwNOU1ExEzARBgNVBAMTCnRlc3QubG9jYWwxIzAhBgkqhkiG9w0BCQEW 19 | FG1yZWlmZXJzb25AZ21haWwuY29tggkArrHvLoDAGYswDAYDVR0TBAUwAwEB/zAN 20 | BgkqhkiG9w0BAQUFAAOCAQEANOYTbanW2iyV1v4oYpcM/y3TWcQKzSME8D2SGFZb 21 | dbMYU81hH3TTlQdvyeh3FAcdjhKE8Xi/RfNNjEslTBscdKXePGpZg6eXRNJzPP5K 22 | KZPf5u6tcpAeUOKrMqbGwbE+h2QixxG1EoVQtE421szsU2P7nHRTdHzKFRnOerfl 23 | Phm3NocR0P40Rv7WKdxpOvqc+XKf0onTruoVYoPWGpwcLixCG0zu4ZQ23/L/Dy18 24 | 4u70Hbq6O/6kq9FBFaDNp3IhiEdu2Cq6ZplU6bL9XDF27KIEErHwtuqBHVlMG+zB 25 | oH/k9vZvwH7OwAjHdKp+1yeZFLYC8K5hjFIHqcdwpZCNIg== 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /nsqd/test/certs/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAuoN3PINU2vofUyG52xtGkBchan1RKVG1XttTmZEQ9NGig9Qz 3 | zbwlhoyWE6Bp/P8IBb378xd9q/kIhf2gf0aGAOa2oAifFhq+sCuxweubf3z9wnlD 4 | Z5q2ro4ud+IDJMS2KcbbhH3ZTrU4AE7/qUHfrScBY3zVwm6s308QgxcNpb4Jt7UL 5 | zGF6ug7UotGGCK7IcKqixkRQOEKyQFBuwoxndKHy3FxT0cCB66BU16bNAG7/BhrY 6 | NIPlo55MxeUqj7Gs1y/INbc+JbVdHOVWW+S35oZ+hS2KPonCQaVOiqfroppPlkou 7 | ngc1uRKKBg/0sZVUdU3ugC5uRDdOYDAxozfOPQIDAQABAoIBACjjLx198Pk8Qee4 8 | igTleteVqoasyEEVn3wW+sG8kooI9uaNe3nLmDJh2Xid/v4ubnTLzFLjQHKV2m99 9 | RVUBgdjquvhkS3POEyWNvp8zZlhRb1PVv5gTy56Cnt87FdNWqFSKOo9WA3CEu60d 10 | pGBXh5Bu75f+wuGn34/oxkbmo+BEY094/cYz0xRJn5k8v6MA0Ru49x3ZjnP0fJaK 11 | P0rBugIC7rJAgSwXKrp7LOsSdvJ4ArplL1M9yFRQt5iaA6v8Yygkr7EILE06iydu 12 | CozbBJrEhha2/lup3OXeQF0EPoQn7lQ3sIigkLacdgIKeUhvBE7PPXBeUj+4nZ1g 13 | WyIMCoUCgYEA97e9iZyjEYXe20DCyZDA3WE5apcdRTST6FYUOB+waEdhzl3NHlQT 14 | su9vj3GXJ1UOw/XvwD3+q0fC+SWvhJvy8e1o+Tr5RTeGutu5n9zeCzLJf7D/xfAT 15 | miEkKWkysemcbMQ/5jTJ6m9/+t8q1xtOiSlL/8mIdcoeof95ShQrNGMCgYEAwL/e 16 | LxO45mU6waYeVGmGGuA4vMIJgx7bc0fJLfDMMiSFP5NL9DXy9rD2SIV8gC4lvxwS 17 | naX/BpygZu9905p5BWvR0sekBcU83TLysZSBVGoptZcPOXPAgPmxYDryDZ1LLK0M 18 | Y897Gc1NaXTlcseKJzDJ7JUL9A6ifoxn/qBv5N8CgYAKcolf4GdQOeEoRhbxAVXh 19 | AFS3lh/55znFtrurkkqSW1BYr8QS32DlTwvZdOT3F7NDuH+gD/1JpAEpGNnKMfmR 20 | En425LHWpXzdLJ8ritkih8XPyNtAsdthyLwJsxIcRsn+HFjFkLxjUsiHABqCb/hj 21 | +IXoB8vli9y24FtxMGCSGQKBgQCww8S5G7cn45Ic4YFTHGsV3qdpw/EBRJ0wfZbs 22 | ALbjDUb95NF1Jryoq+VQ4MoqqyeGufIAjajxoVYg0GVuV23BIP4N9XRgrl+A5CZZ 23 | 4L0ycyHJHL2jjJnwLg0TGTu+bI/yasjACBj2sqbZnZCG0KPMCRxFUPLjYUB4Gb+N 24 | bgjvtQKBgH4FwnbA/WZsd71yrpjt2VTJgNl7MLuKFXmHtphrTVk/No0m+Pt2bC4K 25 | JsUm4vd5n2znoLBprO2hvXIKXX30/wl/8WPNDLCoY4mG0p/t4U0zNEp6gpP0rLbM 26 | nhGMuk9aAqiJgJhgyDiQ/MSwQgHx8LD4W04/VSRkSSeZmpY2ZB91 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /nsqd/test/certs/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAnX0KB+svwy+yHU2qggz/EaGgcraKShagKo+9M9y5HLM852ng 3 | k5c+t+tJJbx3N954Wr1FXBuGIv1ltU05rU4zhvBS25tVP1UIEnT5pBt2TeetLkl1 4 | 99Y7fxh1hKmnwJMG3fy3VZdNXEndBombXMmtXpQYshuEJHKeUNDbQKz5X+GjEdkT 5 | PO/HY/VMHsxS23pbSimQozMg3hvLIdgv0aS3QECzydZBgTPThy3uDtHIuCpxCwXd 6 | /vDF68ATlYgo3h3lh2vxNwM/pjklIUhzMh4XaKQF7m3/0KbtUcXfy0QHueeuMr11 7 | E9MAFNyRN4xf9Fk1yB97KJ3PJBTC5WD/m1nW+QIDAQABAoIBACvtfKbIywG+hAf4 8 | ad7skRjx5DcbA2e29+XnQfb9UgTXWd2SgrmoLi5OypBkCTzkKN3mfTo70yZfV8dC 9 | Sxwz+9tfnTz0DssjhKThS+CiaFVCkeOfSfBfKSlCQUVHrSrh18CDhP+yvDlJwQTZ 10 | zSQMfPcsh9bmJe2kqtQP7ZgUp1o+vaB8Sju8YYrO6FllxbdLRGm4pfvvrHIRRmXa 11 | oVHn0ei0JpwoTY9kHYht4LNeJnbP/MCWdmcuv3Gnel7jAlhaKab5aNIGr0Xe7aIQ 12 | iX6mpZ0/Rnt8o/XcTOg8l3ruIdVuySX6SYn08JMnfFkXdNYRVhoV1tC5ElWkaZLf 13 | hPmj2yECgYEAyts0R0b8cZ6HTAyuLm3ilw0s0v0/MM9ZtaqMRilr2WEtAhF0GpHG 14 | TzmGnii0WcTNXD7NTsNcECR/0ZpXPRleMczsL2Juwd4FkQ37h7hdKPseJNrfyHRg 15 | VolOFBX9H14C3wMB9cwdsG4Egw7fE27WCoreEquHgwFxl1zBrXKH088CgYEAxr8w 16 | BKZs0bF7LRrFT5pH8hpMLYHMYk8ZIOfgmEGVBKDQCOERPR9a9kqUss7wl/98LVNK 17 | RnFlyWD6Z0/QcQsLL4LjBeZJ25qEMc6JXm9VGAzhXA1ZkUofVoYCnG+f6KUn8CuJ 18 | /AcV2ZDFsEP10IiQG0hKsceXiwFEvEr8306tMrcCgYBLgnscSR0xAeyk71dq6vZc 19 | ecgEpcX+2kAvclOSzlpZ6WVCjtKkDT0/Qk+M0eQIQkybGLl9pxS+4Yc+s2/jy2yX 20 | pwsHvGE0AvwZeZX2eDcdSRR4bYy9ZixyKdwJeAHnyivRbaIuJ5Opl9pQGpoI9snv 21 | 1K9DTdw8dK4exKVHdgl/WwKBgDkmLsuXg4EEtPOyV/xc08VVNIR9Z2T5c7NXmeiO 22 | KyiKiWeUOF3ID2L07S9BfENozq9F3PzGjMtMXJSqibiHwW6nB1rh7mj8VHjx9+Q0 23 | xVZGFeNfX1r84mgB3uxW2LeQDhzsmB/lda37CC14TU3qhu2hawEV8IijE73FHlOk 24 | Dv+fAoGAI4/XO5o5tNn5Djo8gHmGMCbinUE9+VySxl7wd7PK8w2VSofO88ofixDk 25 | NX94yBYhg5WZcLdPm45RyUnq+WVQYz9IKUrdxLFTH+wxyzUqZCW7jgXCvWV+071q 26 | vqm9C+kndq+18/1VKuCSGWnF7Ay4lbsgPXY2s4VKRxcb3QpZSPU= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /dist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 1. commit to bump the version and update the changelog/readme 4 | # 2. tag that commit 5 | # 3. use dist.sh to produce tar.gz for linux and darwin 6 | # 4. upload *.tar.gz to our bitly s3 bucket 7 | # 5. docker push nsqio/nsq 8 | # 6. push to nsqio/master 9 | # 7. update the release metadata on github / upload the binaries there too 10 | # 8. update the gh-pages branch with versions / download links 11 | # 9. update homebrew version 12 | # 10. send release announcement emails 13 | # 11. update IRC channel topic 14 | # 12. tweet 15 | 16 | set -e 17 | 18 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 19 | rm -rf $DIR/dist/docker 20 | mkdir -p $DIR/dist/docker 21 | rm -rf $DIR/.godeps 22 | mkdir -p $DIR/.godeps 23 | export GOPATH=$DIR/.godeps:$GOPATH 24 | GOPATH=$DIR/.godeps gpm install 25 | 26 | GOFLAGS='-ldflags="-s -w"' 27 | arch=$(go env GOARCH) 28 | version=$(awk '/const Binary/ {print $NF}' < $DIR/internal/version/binary.go | sed 's/"//g') 29 | goversion=$(go version | awk '{print $3}') 30 | 31 | echo "... running tests" 32 | ./test.sh 33 | 34 | for os in linux darwin freebsd windows; do 35 | echo "... building v$version for $os/$arch" 36 | BUILD=$(mktemp -d -t nsq) 37 | TARGET="nsq-$version.$os-$arch.$goversion" 38 | GOOS=$os GOARCH=$arch CGO_ENABLED=0 \ 39 | make DESTDIR=$BUILD PREFIX=/$TARGET GOFLAGS="$GOFLAGS" install 40 | pushd $BUILD 41 | if [ "$os" == "linux" ]; then 42 | cp -r $TARGET/bin $DIR/dist/docker/ 43 | fi 44 | tar czvf $TARGET.tar.gz $TARGET 45 | mv $TARGET.tar.gz $DIR/dist 46 | popd 47 | make clean 48 | rm -r $BUILD 49 | done 50 | 51 | docker build -t nsqio/nsq:v$version . 52 | if [[ ! $version == *"-"* ]]; then 53 | echo "Tagging nsqio/nsq:v$version as the latest release." 54 | docker tag -f nsqio/nsq:v$version nsqio/nsq:latest 55 | fi 56 | -------------------------------------------------------------------------------- /nsqadmin/static/js/models/channel.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | 3 | var AppState = require('../app_state'); 4 | var Backbone = require('backbone'); 5 | 6 | var Channel = Backbone.Model.extend({ 7 | idAttribute: 'name', 8 | 9 | constructor: function Channel() { 10 | Backbone.Model.prototype.constructor.apply(this, arguments); 11 | }, 12 | 13 | url: function() { 14 | return AppState.url('/topics/' + 15 | encodeURIComponent(this.get('topic')) + '/' + 16 | encodeURIComponent(this.get('name'))); 17 | }, 18 | 19 | parse: function(response) { 20 | response['nodes'] = _.map(response['nodes'] || [], function(node) { 21 | var nodeParts = node['node'].split(':'); 22 | var port = nodeParts.pop(); 23 | var address = nodeParts.join(':'); 24 | var hostname = node['hostname']; 25 | node['show_broadcast_address'] = hostname.toLowerCase() !== address.toLowerCase(); 26 | node['hostname_port'] = hostname + ':' + port; 27 | return node; 28 | }); 29 | 30 | response['clients'] = _.map(response['clients'] || [], function(client) { 31 | var clientId = client['client_id']; 32 | var hostname = client['hostname']; 33 | var shortHostname = hostname.split('.')[0]; 34 | 35 | // ignore client_id if it's duplicative 36 | client['show_client_id'] = (clientId.toLowerCase() !== shortHostname.toLowerCase() 37 | && clientId.toLowerCase() !== hostname.toLowerCase()); 38 | 39 | var port = client['remote_address'].split(':').pop(); 40 | client['hostname_port'] = hostname + ':' + port; 41 | 42 | return client; 43 | }); 44 | 45 | return response; 46 | } 47 | }); 48 | 49 | module.exports = Channel; 50 | -------------------------------------------------------------------------------- /internal/pqueue/pqueue_test.go: -------------------------------------------------------------------------------- 1 | package pqueue 2 | 3 | import ( 4 | "container/heap" 5 | "math/rand" 6 | "path/filepath" 7 | "reflect" 8 | "runtime" 9 | "sort" 10 | "testing" 11 | ) 12 | 13 | func equal(t *testing.T, act, exp interface{}) { 14 | if !reflect.DeepEqual(exp, act) { 15 | _, file, line, _ := runtime.Caller(1) 16 | t.Logf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", 17 | filepath.Base(file), line, exp, act) 18 | t.FailNow() 19 | } 20 | } 21 | 22 | func TestPriorityQueue(t *testing.T) { 23 | c := 100 24 | pq := New(c) 25 | 26 | for i := 0; i < c+1; i++ { 27 | heap.Push(&pq, &Item{Value: i, Priority: int64(i)}) 28 | } 29 | equal(t, pq.Len(), c+1) 30 | equal(t, cap(pq), c*2) 31 | 32 | for i := 0; i < c+1; i++ { 33 | item := heap.Pop(&pq) 34 | equal(t, item.(*Item).Value.(int), i) 35 | } 36 | equal(t, cap(pq), c/4) 37 | } 38 | 39 | func TestUnsortedInsert(t *testing.T) { 40 | c := 100 41 | pq := New(c) 42 | ints := make([]int, 0, c) 43 | 44 | for i := 0; i < c; i++ { 45 | v := rand.Int() 46 | ints = append(ints, v) 47 | heap.Push(&pq, &Item{Value: i, Priority: int64(v)}) 48 | } 49 | equal(t, pq.Len(), c) 50 | equal(t, cap(pq), c) 51 | 52 | sort.Sort(sort.IntSlice(ints)) 53 | 54 | for i := 0; i < c; i++ { 55 | item, _ := pq.PeekAndShift(int64(ints[len(ints)-1])) 56 | equal(t, item.Priority, int64(ints[i])) 57 | } 58 | } 59 | 60 | func TestRemove(t *testing.T) { 61 | c := 100 62 | pq := New(c) 63 | 64 | for i := 0; i < c; i++ { 65 | v := rand.Int() 66 | heap.Push(&pq, &Item{Value: "test", Priority: int64(v)}) 67 | } 68 | 69 | for i := 0; i < 10; i++ { 70 | heap.Remove(&pq, rand.Intn((c-1)-i)) 71 | } 72 | 73 | lastPriority := heap.Pop(&pq).(*Item).Priority 74 | for i := 0; i < (c - 10 - 1); i++ { 75 | item := heap.Pop(&pq) 76 | equal(t, lastPriority < item.(*Item).Priority, true) 77 | lastPriority = item.(*Item).Priority 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /nsqadmin/options.go: -------------------------------------------------------------------------------- 1 | package nsqadmin 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "time" 7 | ) 8 | 9 | type Options struct { 10 | HTTPAddress string `flag:"http-address"` 11 | 12 | GraphiteURL string `flag:"graphite-url"` 13 | ProxyGraphite bool `flag:"proxy-graphite"` 14 | 15 | UseStatsdPrefixes bool `flag:"use-statsd-prefixes"` 16 | StatsdPrefix string `flag:"statsd-prefix"` 17 | StatsdCounterFormat string `flag:"statsd-counter-format"` 18 | StatsdGaugeFormat string `flag:"statsd-gauge-format"` 19 | 20 | StatsdInterval time.Duration `flag:"statsd-interval"` 21 | 22 | NSQLookupdHTTPAddresses []string `flag:"lookupd-http-address" cfg:"nsqlookupd_http_addresses"` 23 | NSQDHTTPAddresses []string `flag:"nsqd-http-address" cfg:"nsqd_http_addresses"` 24 | 25 | HTTPClientConnectTimeout time.Duration `flag:"http-client-connect-timeout"` 26 | HTTPClientRequestTimeout time.Duration `flag:"http-client-request-timeout"` 27 | 28 | HTTPClientTLSInsecureSkipVerify bool `flag:"http-client-tls-insecure-skip-verify"` 29 | HTTPClientTLSRootCAFile string `flag:"http-client-tls-root-ca-file"` 30 | HTTPClientTLSCert string `flag:"http-client-tls-cert"` 31 | HTTPClientTLSKey string `flag:"http-client-tls-key"` 32 | 33 | NotificationHTTPEndpoint string `flag:"notification-http-endpoint"` 34 | 35 | Logger Logger 36 | } 37 | 38 | func NewOptions() *Options { 39 | return &Options{ 40 | HTTPAddress: "0.0.0.0:4171", 41 | UseStatsdPrefixes: true, 42 | StatsdPrefix: "nsq.%s", 43 | StatsdCounterFormat: "stats.counters.%s.count", 44 | StatsdGaugeFormat: "stats.gauges.%s", 45 | StatsdInterval: 60 * time.Second, 46 | HTTPClientConnectTimeout: 2 * time.Second, 47 | HTTPClientRequestTimeout: 5 * time.Second, 48 | Logger: log.New(os.Stderr, "[nsqadmin] ", log.Ldate|log.Ltime|log.Lmicroseconds), 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /nsqd/in_flight_pqueue_test.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "sort" 7 | "testing" 8 | 9 | "github.com/nsqio/nsq/internal/test" 10 | ) 11 | 12 | func TestPriorityQueue(t *testing.T) { 13 | c := 100 14 | pq := newInFlightPqueue(c) 15 | 16 | for i := 0; i < c+1; i++ { 17 | pq.Push(&Message{clientID: int64(i), pri: int64(i)}) 18 | } 19 | test.Equal(t, c+1, len(pq)) 20 | test.Equal(t, c*2, cap(pq)) 21 | 22 | for i := 0; i < c+1; i++ { 23 | msg := pq.Pop() 24 | test.Equal(t, int64(i), msg.clientID) 25 | } 26 | test.Equal(t, c/4, cap(pq)) 27 | } 28 | 29 | func TestUnsortedInsert(t *testing.T) { 30 | c := 100 31 | pq := newInFlightPqueue(c) 32 | ints := make([]int, 0, c) 33 | 34 | for i := 0; i < c; i++ { 35 | v := rand.Int() 36 | ints = append(ints, v) 37 | pq.Push(&Message{pri: int64(v)}) 38 | } 39 | test.Equal(t, c, len(pq)) 40 | test.Equal(t, c, cap(pq)) 41 | 42 | sort.Sort(sort.IntSlice(ints)) 43 | 44 | for i := 0; i < c; i++ { 45 | msg, _ := pq.PeekAndShift(int64(ints[len(ints)-1])) 46 | test.Equal(t, int64(ints[i]), msg.pri) 47 | } 48 | } 49 | 50 | func TestRemove(t *testing.T) { 51 | c := 100 52 | pq := newInFlightPqueue(c) 53 | 54 | msgs := make(map[MessageID]*Message) 55 | for i := 0; i < c; i++ { 56 | m := &Message{pri: int64(rand.Intn(100000000))} 57 | copy(m.ID[:], fmt.Sprintf("%016d", m.pri)) 58 | msgs[m.ID] = m 59 | pq.Push(m) 60 | } 61 | 62 | for i := 0; i < 10; i++ { 63 | idx := rand.Intn((c - 1) - i) 64 | var fm *Message 65 | for _, m := range msgs { 66 | if m.index == idx { 67 | fm = m 68 | break 69 | } 70 | } 71 | rm := pq.Remove(idx) 72 | test.Equal(t, fmt.Sprintf("%s", fm.ID), fmt.Sprintf("%s", rm.ID)) 73 | } 74 | 75 | lastPriority := pq.Pop().pri 76 | for i := 0; i < (c - 10 - 1); i++ { 77 | msg := pq.Pop() 78 | test.Equal(t, true, lastPriority <= msg.pri) 79 | lastPriority = msg.pri 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /internal/test/fakes.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | type FakeNetConn struct { 9 | ReadFunc func([]byte) (int, error) 10 | WriteFunc func([]byte) (int, error) 11 | CloseFunc func() error 12 | LocalAddrFunc func() net.Addr 13 | RemoteAddrFunc func() net.Addr 14 | SetDeadlineFunc func(time.Time) error 15 | SetReadDeadlineFunc func(time.Time) error 16 | SetWriteDeadlineFunc func(time.Time) error 17 | } 18 | 19 | func (f FakeNetConn) Read(b []byte) (int, error) { return f.ReadFunc(b) } 20 | func (f FakeNetConn) Write(b []byte) (int, error) { return f.WriteFunc(b) } 21 | func (f FakeNetConn) Close() error { return f.CloseFunc() } 22 | func (f FakeNetConn) LocalAddr() net.Addr { return f.LocalAddrFunc() } 23 | func (f FakeNetConn) RemoteAddr() net.Addr { return f.RemoteAddrFunc() } 24 | func (f FakeNetConn) SetDeadline(t time.Time) error { return f.SetDeadlineFunc(t) } 25 | func (f FakeNetConn) SetReadDeadline(t time.Time) error { return f.SetReadDeadlineFunc(t) } 26 | func (f FakeNetConn) SetWriteDeadline(t time.Time) error { return f.SetWriteDeadlineFunc(t) } 27 | 28 | type fakeNetAddr struct{} 29 | 30 | func (fakeNetAddr) Network() string { return "" } 31 | func (fakeNetAddr) String() string { return "" } 32 | 33 | func NewFakeNetConn() FakeNetConn { 34 | netAddr := fakeNetAddr{} 35 | return FakeNetConn{ 36 | ReadFunc: func(b []byte) (int, error) { return 0, nil }, 37 | WriteFunc: func(b []byte) (int, error) { return len(b), nil }, 38 | CloseFunc: func() error { return nil }, 39 | LocalAddrFunc: func() net.Addr { return netAddr }, 40 | RemoteAddrFunc: func() net.Addr { return netAddr }, 41 | SetDeadlineFunc: func(time.Time) error { return nil }, 42 | SetWriteDeadlineFunc: func(time.Time) error { return nil }, 43 | SetReadDeadlineFunc: func(time.Time) error { return nil }, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /nsqadmin/notify.go: -------------------------------------------------------------------------------- 1 | package nsqadmin 2 | 3 | import ( 4 | "encoding/base64" 5 | "net/http" 6 | "net/url" 7 | "os" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | type AdminAction struct { 13 | Action string `json:"action"` 14 | Topic string `json:"topic"` 15 | Channel string `json:"channel,omitempty"` 16 | Node string `json:"node,omitempty"` 17 | Timestamp int64 `json:"timestamp"` 18 | User string `json:"user,omitempty"` 19 | RemoteIP string `json:"remote_ip"` 20 | UserAgent string `json:"user_agent"` 21 | URL string `json:"url"` // The URL of the HTTP request that triggered this action 22 | Via string `json:"via"` // the Hostname of the nsqadmin performing this action 23 | } 24 | 25 | func basicAuthUser(req *http.Request) string { 26 | s := strings.SplitN(req.Header.Get("Authorization"), " ", 2) 27 | if len(s) != 2 || s[0] != "Basic" { 28 | return "" 29 | } 30 | b, err := base64.StdEncoding.DecodeString(s[1]) 31 | if err != nil { 32 | return "" 33 | } 34 | pair := strings.SplitN(string(b), ":", 2) 35 | if len(pair) != 2 { 36 | return "" 37 | } 38 | return pair[0] 39 | } 40 | 41 | func (s *httpServer) notifyAdminAction(action, topic, channel, node string, req *http.Request) { 42 | if s.ctx.nsqadmin.getOpts().NotificationHTTPEndpoint == "" { 43 | return 44 | } 45 | via, _ := os.Hostname() 46 | 47 | u := url.URL{ 48 | Scheme: "http", 49 | Host: req.Host, 50 | Path: req.URL.Path, 51 | RawQuery: req.URL.RawQuery, 52 | } 53 | if req.TLS != nil || req.Header.Get("X-Scheme") == "https" { 54 | u.Scheme = "https" 55 | } 56 | 57 | a := &AdminAction{ 58 | Action: action, 59 | Topic: topic, 60 | Channel: channel, 61 | Node: node, 62 | Timestamp: time.Now().Unix(), 63 | User: basicAuthUser(req), 64 | RemoteIP: req.RemoteAddr, 65 | UserAgent: req.UserAgent(), 66 | URL: u.String(), 67 | Via: via, 68 | } 69 | // Perform all work in a new goroutine so this never blocks 70 | go func() { s.ctx.nsqadmin.notifications <- a }() 71 | } 72 | -------------------------------------------------------------------------------- /apps/nsq_to_file/strftime.go: -------------------------------------------------------------------------------- 1 | // COPIED FROM https://github.com/jehiah/go-strftime 2 | package main 3 | 4 | import ( 5 | "time" 6 | ) 7 | 8 | // taken from time/format.go 9 | var conversion = map[string]string{ 10 | /*stdLongMonth */ "B": "January", 11 | /*stdMonth */ "b": "Jan", 12 | // stdNumMonth */ "m": "1", 13 | /*stdZeroMonth */ "m": "01", 14 | /*stdLongWeekDay */ "A": "Monday", 15 | /*stdWeekDay */ "a": "Mon", 16 | // stdDay */ "d": "2", 17 | // stdUnderDay */ "d": "_2", 18 | /*stdZeroDay */ "d": "02", 19 | /*stdHour */ "H": "15", 20 | // stdHour12 */ "I": "3", 21 | /*stdZeroHour12 */ "I": "03", 22 | // stdMinute */ "M": "4", 23 | /*stdZeroMinute */ "M": "04", 24 | // stdSecond */ "S": "5", 25 | /*stdZeroSecond */ "S": "05", 26 | /*stdLongYear */ "Y": "2006", 27 | /*stdYear */ "y": "06", 28 | /*stdPM */ "p": "PM", 29 | // stdpm */ "p": "pm", 30 | /*stdTZ */ "Z": "MST", 31 | // stdISO8601TZ */ "z": "Z0700", // prints Z for UTC 32 | // stdISO8601ColonTZ */ "z": "Z07:00", // prints Z for UTC 33 | /*stdNumTZ */ "z": "-0700", // always numeric 34 | // stdNumShortTZ */ "b": "-07", // always numeric 35 | // stdNumColonTZ */ "b": "-07:00", // always numeric 36 | "%": "%", 37 | } 38 | 39 | // This is an alternative to time.Format because no one knows 40 | // what date 040305 is supposed to create when used as a 'layout' string 41 | // this takes standard strftime format options. For a complete list 42 | // of format options see http://strftime.org/ 43 | func strftime(format string, t time.Time) string { 44 | layout := "" 45 | length := len(format) 46 | for i := 0; i < length; i++ { 47 | if format[i] == '%' && i <= length-2 { 48 | if layoutCmd, ok := conversion[format[i+1:i+2]]; ok { 49 | layout = layout + layoutCmd 50 | i++ 51 | continue 52 | } 53 | } 54 | layout = layout + format[i:i+1] 55 | } 56 | return t.Format(layout) 57 | } 58 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/topic.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | 3 | window.jQuery = $; 4 | var bootstrap = require('bootstrap'); //eslint-disable-line no-unused-vars 5 | var bootbox = require('bootbox'); 6 | 7 | var Pubsub = require('../lib/pubsub'); 8 | var AppState = require('../app_state'); 9 | 10 | var BaseView = require('./base'); 11 | 12 | var TopicView = BaseView.extend({ 13 | className: 'topic container-fluid', 14 | 15 | template: require('./spinner.hbs'), 16 | 17 | events: { 18 | 'click .topic-actions button': 'topicAction' 19 | }, 20 | 21 | initialize: function() { 22 | BaseView.prototype.initialize.apply(this, arguments); 23 | this.listenTo(AppState, 'change:graph_interval', this.render); 24 | this.model.fetch() 25 | .done(function(data) { 26 | this.template = require('./topic.hbs'); 27 | this.render({'message': data['message']}); 28 | }.bind(this)) 29 | .fail(this.handleViewError.bind(this)) 30 | .always(Pubsub.trigger.bind(Pubsub, 'view:ready')); 31 | }, 32 | 33 | topicAction: function(e) { 34 | e.preventDefault(); 35 | e.stopPropagation(); 36 | var action = $(e.currentTarget).data('action'); 37 | var txt = 'Are you sure you want to ' + 38 | action + ' ' + this.model.get('name') + '?'; 39 | bootbox.confirm(txt, function(result) { 40 | if (result !== true) { 41 | return; 42 | } 43 | if (action === 'delete') { 44 | $.ajax(this.model.url(), {'method': 'DELETE'}) 45 | .done(function() { window.location = '/'; }); 46 | } else { 47 | $.post(this.model.url(), JSON.stringify({'action': action})) 48 | .done(function() { window.location.reload(true); }) 49 | .fail(this.handleAJAXError.bind(this)); 50 | } 51 | }.bind(this)); 52 | } 53 | }); 54 | 55 | module.exports = TopicView; 56 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open and 4 | welcoming community, we pledge to respect all people who contribute through reporting issues, 5 | posting feature requests, updating documentation, submitting pull requests or patches, and other 6 | activities. 7 | 8 | We are committed to making participation in this project a harassment-free experience for everyone, 9 | regardless of level of experience, gender, gender identity and expression, sexual orientation, 10 | disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. 11 | 12 | Examples of unacceptable behavior by participants include: 13 | 14 | * The use of sexualized language or imagery 15 | * Personal attacks 16 | * Trolling or insulting/derogatory comments 17 | * Public or private harassment 18 | * Publishing other's private information, such as physical or electronic addresses, without explicit permission 19 | * Other unethical or unprofessional conduct. 20 | 21 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, 22 | code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By 23 | adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently 24 | applying these principles to every aspect of managing this project. Project maintainers who do not 25 | follow or enforce the Code of Conduct may be permanently removed from the project team. 26 | 27 | This code of conduct applies both within project spaces and in public spaces when an individual is 28 | representing the project or its community. 29 | 30 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an 31 | issue or contacting one or more of the project maintainers. 32 | 33 | This Code of Conduct is adapted from the [Contributor Covenant][1], version 1.2.0, available at 34 | [http://contributor-covenant.org/version/1/2/0/][2]. 35 | 36 | [1]: http://contributor-covenant.org 37 | [2]: http://contributor-covenant.org/version/1/2/0/ 38 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/header.hbs: -------------------------------------------------------------------------------- 1 | 39 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/nodes.hbs: -------------------------------------------------------------------------------- 1 | {{> warning}} 2 | {{> error}} 3 | 4 |
5 |
6 |

NSQd Nodes ({{collection.length}})

7 |
8 |
9 | 10 |
11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {{#if nsqlookupd.length}} 20 | 21 | {{/if}} 22 | 23 | 24 | {{#each collection}} 25 | 26 | 27 | 28 | 29 | 30 | 31 | {{#if ../nsqlookupd.length}} 32 | 38 | {{/if}} 39 | 47 | 48 | {{/each}} 49 |
HostnameBroadcast AddressTCP PortHTTP PortVersionLookupd Conns.Topics
{{hostname}}{{broadcast_address}}{{tcp_port}}{{http_port}}{{version}} 33 | {{remote_addresses.length}} 34 |
35 | {{#each remote_addresses}}{{this}}
{{/each}} 36 |
37 |
40 | {{#if topics.length}} 41 | {{topics.length}} 42 | {{#each topics}} 43 | {{topic}} 44 | {{/each}} 45 | {{/if}} 46 |
50 |
51 |
52 | -------------------------------------------------------------------------------- /nsqd/in_flight_pqueue.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | type inFlightPqueue []*Message 4 | 5 | func newInFlightPqueue(capacity int) inFlightPqueue { 6 | return make(inFlightPqueue, 0, capacity) 7 | } 8 | 9 | func (pq inFlightPqueue) Swap(i, j int) { 10 | pq[i], pq[j] = pq[j], pq[i] 11 | pq[i].index = i 12 | pq[j].index = j 13 | } 14 | 15 | func (pq *inFlightPqueue) Push(x *Message) { 16 | n := len(*pq) 17 | c := cap(*pq) 18 | if n+1 > c { 19 | npq := make(inFlightPqueue, n, c*2) 20 | copy(npq, *pq) 21 | *pq = npq 22 | } 23 | *pq = (*pq)[0 : n+1] 24 | x.index = n 25 | (*pq)[n] = x 26 | pq.up(n) 27 | } 28 | 29 | func (pq *inFlightPqueue) Pop() *Message { 30 | n := len(*pq) 31 | c := cap(*pq) 32 | pq.Swap(0, n-1) 33 | pq.down(0, n-1) 34 | if n < (c/2) && c > 25 { 35 | npq := make(inFlightPqueue, n, c/2) 36 | copy(npq, *pq) 37 | *pq = npq 38 | } 39 | x := (*pq)[n-1] 40 | x.index = -1 41 | *pq = (*pq)[0 : n-1] 42 | return x 43 | } 44 | 45 | func (pq *inFlightPqueue) Remove(i int) *Message { 46 | n := len(*pq) 47 | if n-1 != i { 48 | pq.Swap(i, n-1) 49 | pq.down(i, n-1) 50 | pq.up(i) 51 | } 52 | x := (*pq)[n-1] 53 | x.index = -1 54 | *pq = (*pq)[0 : n-1] 55 | return x 56 | } 57 | 58 | func (pq *inFlightPqueue) PeekAndShift(max int64) (*Message, int64) { 59 | if len(*pq) == 0 { 60 | return nil, 0 61 | } 62 | 63 | x := (*pq)[0] 64 | if x.pri > max { 65 | return nil, x.pri - max 66 | } 67 | pq.Pop() 68 | 69 | return x, 0 70 | } 71 | 72 | func (pq *inFlightPqueue) up(j int) { 73 | for { 74 | i := (j - 1) / 2 // parent 75 | if i == j || (*pq)[j].pri >= (*pq)[i].pri { 76 | break 77 | } 78 | pq.Swap(i, j) 79 | j = i 80 | } 81 | } 82 | 83 | func (pq *inFlightPqueue) down(i, n int) { 84 | for { 85 | j1 := 2*i + 1 86 | if j1 >= n || j1 < 0 { // j1 < 0 after int overflow 87 | break 88 | } 89 | j := j1 // left child 90 | if j2 := j1 + 1; j2 < n && (*pq)[j1].pri >= (*pq)[j2].pri { 91 | j = j2 // = 2*i + 2 // right child 92 | } 93 | if (*pq)[j].pri >= (*pq)[i].pri { 94 | break 95 | } 96 | pq.Swap(i, j) 97 | i = j 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /nsqadmin/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true 4 | }, 5 | // Rule docs: http://eslint.org/docs/rules/ 6 | "rules": { 7 | "block-scoped-var": [1], 8 | "brace-style": [1, "1tbs", {"allowSingleLine": true}], 9 | "camelcase": [1], 10 | "comma-spacing": [1], 11 | "comma-style": [1], 12 | "computed-property-spacing": [1, "never"], 13 | "consistent-return": [1], 14 | "consistent-this": [1, "self"], 15 | "curly": [2], 16 | "dot-notation": [0], 17 | "eol-last": [1], 18 | "eqeqeq": [1], 19 | "indent": [1, 4], 20 | "key-spacing": [1], 21 | "max-len": [1, 100], 22 | "max-nested-callbacks": [2, 3], // ???? 23 | "new-cap": [1], 24 | "new-parens": [1], 25 | "no-caller": [2], 26 | "no-console": [0], 27 | "no-eval": [2], 28 | "no-extend-native": [2], 29 | "no-extra-bind": [1], 30 | "no-floating-decimal": [1], 31 | "no-iterator": [1], 32 | "no-lone-blocks": [1], 33 | "no-lonely-if": [1], 34 | "no-mixed-requires": [0], 35 | "no-mixed-spaces-and-tabs": [1], 36 | "no-multi-spaces": [1], 37 | "no-multi-str": [1], 38 | "no-multiple-empty-lines": [2, {"max": 2}], 39 | "no-native-reassign": [1], 40 | "no-new": [0], 41 | "no-redeclare": [1], 42 | "no-shadow": [1], 43 | "no-spaced-func": [1], 44 | "no-throw-literal": [1], 45 | "no-trailing-spaces": [1], 46 | "no-undef": [1], 47 | "no-underscore-dangle": [0], 48 | "no-unneeded-ternary": [1], 49 | "no-unused-vars": [1], 50 | "no-use-before-define": [1, "nofunc"], 51 | "no-with": [2], 52 | "one-var": [1, "never"], 53 | "quotes": [1, "single"], 54 | "radix": [1], 55 | "semi": [1, "always"], 56 | "semi-spacing": [1], 57 | "space-after-function-name": [1, "never"], 58 | "space-after-keywords": [1, "always"], 59 | "space-before-blocks": [1, "always"], 60 | "space-before-function-paren": [1, "never"], 61 | "space-in-parens": [1, "never"], 62 | "space-infix-ops": [1], 63 | "space-return-throw-case": [1], 64 | "space-unary-ops": [1], 65 | "strict": [0], 66 | "wrap-iife": [1] 67 | }, 68 | "globals": { 69 | "module": true, 70 | "require": true 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /bench/bench_channels/bench_channels.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "net" 8 | "sync" 9 | "time" 10 | 11 | "github.com/nsqio/go-nsq" 12 | ) 13 | 14 | var ( 15 | num = flag.Int("num", 10000, "num channels") 16 | tcpAddress = flag.String("nsqd-tcp-address", "127.0.0.1:4150", ": to connect to nsqd") 17 | ) 18 | 19 | func main() { 20 | flag.Parse() 21 | var wg sync.WaitGroup 22 | 23 | goChan := make(chan int) 24 | rdyChan := make(chan int) 25 | for j := 0; j < *num; j++ { 26 | wg.Add(1) 27 | go func(id int) { 28 | subWorker(*num, *tcpAddress, fmt.Sprintf("t%d", j), "ch", rdyChan, goChan, id) 29 | wg.Done() 30 | }(j) 31 | <-rdyChan 32 | time.Sleep(5 * time.Millisecond) 33 | } 34 | 35 | close(goChan) 36 | wg.Wait() 37 | } 38 | 39 | func subWorker(n int, tcpAddr string, 40 | topic string, channel string, 41 | rdyChan chan int, goChan chan int, id int) { 42 | conn, err := net.DialTimeout("tcp", tcpAddr, time.Second) 43 | if err != nil { 44 | panic(err.Error()) 45 | } 46 | conn.Write(nsq.MagicV2) 47 | rw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) 48 | ci := make(map[string]interface{}) 49 | ci["short_id"] = "test" 50 | ci["long_id"] = "test" 51 | cmd, _ := nsq.Identify(ci) 52 | cmd.WriteTo(rw) 53 | nsq.Subscribe(topic, channel).WriteTo(rw) 54 | rdyCount := 1 55 | rdy := rdyCount 56 | rdyChan <- 1 57 | <-goChan 58 | nsq.Ready(rdyCount).WriteTo(rw) 59 | rw.Flush() 60 | nsq.ReadResponse(rw) 61 | nsq.ReadResponse(rw) 62 | for { 63 | resp, err := nsq.ReadResponse(rw) 64 | if err != nil { 65 | panic(err.Error()) 66 | } 67 | frameType, data, err := nsq.UnpackResponse(resp) 68 | if err != nil { 69 | panic(err.Error()) 70 | } 71 | if frameType == nsq.FrameTypeError { 72 | panic(string(data)) 73 | } else if frameType == nsq.FrameTypeResponse { 74 | nsq.Nop().WriteTo(rw) 75 | rw.Flush() 76 | continue 77 | } 78 | msg, err := nsq.DecodeMessage(data) 79 | if err != nil { 80 | panic(err.Error()) 81 | } 82 | nsq.Finish(msg.ID).WriteTo(rw) 83 | rdy-- 84 | if rdy == 0 { 85 | nsq.Ready(rdyCount).WriteTo(rw) 86 | rdy = rdyCount 87 | rw.Flush() 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /nsqd/guid.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | // the core algorithm here was borrowed from: 4 | // Blake Mizerany's `noeqd` https://github.com/bmizerany/noeqd 5 | // and indirectly: 6 | // Twitter's `snowflake` https://github.com/twitter/snowflake 7 | 8 | // only minor cleanup and changes to introduce a type, combine the concept 9 | // of workerID + datacenterId into a single identifier, and modify the 10 | // behavior when sequences rollover for our specific implementation needs 11 | 12 | import ( 13 | "encoding/hex" 14 | "errors" 15 | "time" 16 | ) 17 | 18 | const ( 19 | workerIDBits = uint64(10) 20 | sequenceBits = uint64(12) 21 | workerIDShift = sequenceBits 22 | timestampShift = sequenceBits + workerIDBits 23 | sequenceMask = int64(-1) ^ (int64(-1) << sequenceBits) 24 | 25 | // ( 2012-10-28 16:23:42 UTC ).UnixNano() >> 20 26 | twepoch = int64(1288834974288) 27 | ) 28 | 29 | var ErrTimeBackwards = errors.New("time has gone backwards") 30 | var ErrSequenceExpired = errors.New("sequence expired") 31 | var ErrIDBackwards = errors.New("ID went backward") 32 | 33 | type guid int64 34 | 35 | type guidFactory struct { 36 | sequence int64 37 | lastTimestamp int64 38 | lastID guid 39 | } 40 | 41 | func (f *guidFactory) NewGUID(workerID int64) (guid, error) { 42 | // divide by 1048576, giving pseudo-milliseconds 43 | ts := time.Now().UnixNano() >> 20 44 | 45 | if ts < f.lastTimestamp { 46 | return 0, ErrTimeBackwards 47 | } 48 | 49 | if f.lastTimestamp == ts { 50 | f.sequence = (f.sequence + 1) & sequenceMask 51 | if f.sequence == 0 { 52 | return 0, ErrSequenceExpired 53 | } 54 | } else { 55 | f.sequence = 0 56 | } 57 | 58 | f.lastTimestamp = ts 59 | 60 | id := guid(((ts - twepoch) << timestampShift) | 61 | (workerID << workerIDShift) | 62 | f.sequence) 63 | 64 | if id <= f.lastID { 65 | return 0, ErrIDBackwards 66 | } 67 | 68 | f.lastID = id 69 | 70 | return id, nil 71 | } 72 | 73 | func (g guid) Hex() MessageID { 74 | var h MessageID 75 | var b [8]byte 76 | 77 | b[0] = byte(g >> 56) 78 | b[1] = byte(g >> 48) 79 | b[2] = byte(g >> 40) 80 | b[3] = byte(g >> 32) 81 | b[4] = byte(g >> 24) 82 | b[5] = byte(g >> 16) 83 | b[6] = byte(g >> 8) 84 | b[7] = byte(g) 85 | 86 | hex.Encode(h[:], b[:]) 87 | return h 88 | } 89 | -------------------------------------------------------------------------------- /nsqlookupd/nsqlookupd.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "sync" 8 | 9 | "github.com/nsqio/nsq/internal/http_api" 10 | "github.com/nsqio/nsq/internal/protocol" 11 | "github.com/nsqio/nsq/internal/util" 12 | "github.com/nsqio/nsq/internal/version" 13 | ) 14 | 15 | type NSQLookupd struct { 16 | sync.RWMutex 17 | opts *Options 18 | tcpListener net.Listener 19 | httpListener net.Listener 20 | waitGroup util.WaitGroupWrapper 21 | DB *RegistrationDB 22 | } 23 | 24 | func New(opts *Options) *NSQLookupd { 25 | n := &NSQLookupd{ 26 | opts: opts, 27 | DB: NewRegistrationDB(), 28 | } 29 | n.logf(version.String("nsqlookupd")) 30 | return n 31 | } 32 | 33 | func (l *NSQLookupd) logf(f string, args ...interface{}) { 34 | if l.opts.Logger == nil { 35 | return 36 | } 37 | l.opts.Logger.Output(2, fmt.Sprintf(f, args...)) 38 | } 39 | 40 | func (l *NSQLookupd) Main() { 41 | ctx := &Context{l} 42 | 43 | tcpListener, err := net.Listen("tcp", l.opts.TCPAddress) 44 | if err != nil { 45 | l.logf("FATAL: listen (%s) failed - %s", l.opts.TCPAddress, err) 46 | os.Exit(1) 47 | } 48 | l.Lock() 49 | l.tcpListener = tcpListener 50 | l.Unlock() 51 | tcpServer := &tcpServer{ctx: ctx} 52 | l.waitGroup.Wrap(func() { 53 | protocol.TCPServer(tcpListener, tcpServer, l.opts.Logger) 54 | }) 55 | 56 | httpListener, err := net.Listen("tcp", l.opts.HTTPAddress) 57 | if err != nil { 58 | l.logf("FATAL: listen (%s) failed - %s", l.opts.HTTPAddress, err) 59 | os.Exit(1) 60 | } 61 | l.Lock() 62 | l.httpListener = httpListener 63 | l.Unlock() 64 | httpServer := newHTTPServer(ctx) 65 | l.waitGroup.Wrap(func() { 66 | http_api.Serve(httpListener, httpServer, "HTTP", l.opts.Logger) 67 | }) 68 | } 69 | 70 | func (l *NSQLookupd) RealTCPAddr() *net.TCPAddr { 71 | l.RLock() 72 | defer l.RUnlock() 73 | return l.tcpListener.Addr().(*net.TCPAddr) 74 | } 75 | 76 | func (l *NSQLookupd) RealHTTPAddr() *net.TCPAddr { 77 | l.RLock() 78 | defer l.RUnlock() 79 | return l.httpListener.Addr().(*net.TCPAddr) 80 | } 81 | 82 | func (l *NSQLookupd) Exit() { 83 | if l.tcpListener != nil { 84 | l.tcpListener.Close() 85 | } 86 | 87 | if l.httpListener != nil { 88 | l.httpListener.Close() 89 | } 90 | l.waitGroup.Wait() 91 | } 92 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/channel.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | 3 | window.jQuery = $; 4 | var bootstrap = require('bootstrap'); //eslint-disable-line no-unused-vars 5 | var bootbox = require('bootbox'); 6 | 7 | var Pubsub = require('../lib/pubsub'); 8 | var AppState = require('../app_state'); 9 | 10 | var BaseView = require('./base'); 11 | 12 | var ChannelView = BaseView.extend({ 13 | className: 'channel container-fluid', 14 | 15 | template: require('./spinner.hbs'), 16 | 17 | events: { 18 | 'click .channel-actions button': 'channelAction' 19 | }, 20 | 21 | initialize: function() { 22 | BaseView.prototype.initialize.apply(this, arguments); 23 | this.listenTo(AppState, 'change:graph_interval', this.render); 24 | this.model.fetch() 25 | .done(function(data) { 26 | this.template = require('./channel.hbs'); 27 | this.render({'message': data['message']}); 28 | }.bind(this)) 29 | .fail(this.handleViewError.bind(this)) 30 | .always(Pubsub.trigger.bind(Pubsub, 'view:ready')); 31 | }, 32 | 33 | channelAction: function(e) { 34 | e.preventDefault(); 35 | e.stopPropagation(); 36 | var action = $(e.currentTarget).data('action'); 37 | var txt = 'Are you sure you want to ' + 38 | action + ' ' + this.model.get('topic') + 39 | '/' + this.model.get('name') + '?'; 40 | bootbox.confirm(txt, function(result) { 41 | if (result !== true) { 42 | return; 43 | } 44 | if (action === 'delete') { 45 | $.ajax(this.model.url(), {'method': 'DELETE'}) 46 | .done(function() { 47 | window.location = '/topics/' + encodeURIComponent(this.model.get('topic')); 48 | }) 49 | .fail(this.handleAJAXError.bind(this)); 50 | } else { 51 | $.post(this.model.url(), JSON.stringify({'action': action})) 52 | .done(function() { window.location.reload(true); }) 53 | .fail(this.handleAJAXError.bind(this)); 54 | } 55 | }.bind(this)); 56 | } 57 | }); 58 | 59 | module.exports = ChannelView; 60 | -------------------------------------------------------------------------------- /internal/http_api/compress.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // copied from https://github.com/gorilla/handlers/blob/master/compress.go 6 | 7 | package http_api 8 | 9 | import ( 10 | "compress/flate" 11 | "compress/gzip" 12 | "io" 13 | "net/http" 14 | "strings" 15 | ) 16 | 17 | type compressResponseWriter struct { 18 | io.Writer 19 | http.ResponseWriter 20 | http.Hijacker 21 | } 22 | 23 | func (w *compressResponseWriter) Header() http.Header { 24 | return w.ResponseWriter.Header() 25 | } 26 | 27 | func (w *compressResponseWriter) WriteHeader(c int) { 28 | w.ResponseWriter.Header().Del("Content-Length") 29 | w.ResponseWriter.WriteHeader(c) 30 | } 31 | 32 | func (w *compressResponseWriter) Write(b []byte) (int, error) { 33 | h := w.ResponseWriter.Header() 34 | if h.Get("Content-Type") == "" { 35 | h.Set("Content-Type", http.DetectContentType(b)) 36 | } 37 | h.Del("Content-Length") 38 | return w.Writer.Write(b) 39 | } 40 | 41 | // CompressHandler gzip compresses HTTP responses for clients that support it 42 | // via the 'Accept-Encoding' header. 43 | func CompressHandler(h http.Handler) http.Handler { 44 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 45 | L: 46 | for _, enc := range strings.Split(r.Header.Get("Accept-Encoding"), ",") { 47 | switch strings.TrimSpace(enc) { 48 | case "gzip": 49 | w.Header().Set("Content-Encoding", "gzip") 50 | w.Header().Add("Vary", "Accept-Encoding") 51 | 52 | gw := gzip.NewWriter(w) 53 | defer gw.Close() 54 | 55 | h, hok := w.(http.Hijacker) 56 | if !hok { /* w is not Hijacker... oh well... */ 57 | h = nil 58 | } 59 | 60 | w = &compressResponseWriter{ 61 | Writer: gw, 62 | ResponseWriter: w, 63 | Hijacker: h, 64 | } 65 | 66 | break L 67 | case "deflate": 68 | w.Header().Set("Content-Encoding", "deflate") 69 | w.Header().Add("Vary", "Accept-Encoding") 70 | 71 | fw, _ := flate.NewWriter(w, flate.DefaultCompression) 72 | defer fw.Close() 73 | 74 | h, hok := w.(http.Hijacker) 75 | if !hok { /* w is not Hijacker... oh well... */ 76 | h = nil 77 | } 78 | 79 | w = &compressResponseWriter{ 80 | Writer: fw, 81 | ResponseWriter: w, 82 | Hijacker: h, 83 | } 84 | 85 | break L 86 | } 87 | } 88 | 89 | h.ServeHTTP(w, r) 90 | }) 91 | } 92 | -------------------------------------------------------------------------------- /apps/nsqlookupd/nsqlookupd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "syscall" 10 | "time" 11 | 12 | "github.com/BurntSushi/toml" 13 | "github.com/judwhite/go-svc/svc" 14 | "github.com/mreiferson/go-options" 15 | "github.com/nsqio/nsq/internal/version" 16 | "github.com/nsqio/nsq/nsqlookupd" 17 | ) 18 | 19 | var ( 20 | flagSet = flag.NewFlagSet("nsqlookupd", flag.ExitOnError) 21 | 22 | config = flagSet.String("config", "", "path to config file") 23 | showVersion = flagSet.Bool("version", false, "print version string") 24 | verbose = flagSet.Bool("verbose", false, "enable verbose logging") 25 | 26 | tcpAddress = flagSet.String("tcp-address", "0.0.0.0:4160", ": to listen on for TCP clients") 27 | httpAddress = flagSet.String("http-address", "0.0.0.0:4161", ": to listen on for HTTP clients") 28 | broadcastAddress = flagSet.String("broadcast-address", "", "address of this lookupd node, (default to the OS hostname)") 29 | 30 | inactiveProducerTimeout = flagSet.Duration("inactive-producer-timeout", 300*time.Second, "duration of time a producer will remain in the active list since its last ping") 31 | tombstoneLifetime = flagSet.Duration("tombstone-lifetime", 45*time.Second, "duration of time a producer will remain tombstoned if registration remains") 32 | ) 33 | 34 | type program struct { 35 | nsqlookupd *nsqlookupd.NSQLookupd 36 | } 37 | 38 | func main() { 39 | prg := &program{} 40 | if err := svc.Run(prg, syscall.SIGINT, syscall.SIGTERM); err != nil { 41 | log.Fatal(err) 42 | } 43 | } 44 | 45 | func (p *program) Init(env svc.Environment) error { 46 | if env.IsWindowsService() { 47 | dir := filepath.Dir(os.Args[0]) 48 | return os.Chdir(dir) 49 | } 50 | return nil 51 | } 52 | 53 | func (p *program) Start() error { 54 | flagSet.Parse(os.Args[1:]) 55 | 56 | if *showVersion { 57 | fmt.Println(version.String("nsqlookupd")) 58 | os.Exit(0) 59 | } 60 | 61 | var cfg map[string]interface{} 62 | if *config != "" { 63 | _, err := toml.DecodeFile(*config, &cfg) 64 | if err != nil { 65 | log.Fatalf("ERROR: failed to load config file %s - %s", *config, err.Error()) 66 | } 67 | } 68 | 69 | opts := nsqlookupd.NewOptions() 70 | options.Resolve(opts, flagSet, cfg) 71 | daemon := nsqlookupd.New(opts) 72 | 73 | daemon.Main() 74 | p.nsqlookupd = daemon 75 | return nil 76 | } 77 | 78 | func (p *program) Stop() error { 79 | if p.nsqlookupd != nil { 80 | p.nsqlookupd.Exit() 81 | } 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /internal/quantile/aggregate.go: -------------------------------------------------------------------------------- 1 | package quantile 2 | 3 | import ( 4 | "encoding/json" 5 | "math" 6 | "sort" 7 | ) 8 | 9 | type E2eProcessingLatencyAggregate struct { 10 | Count int `json:"count"` 11 | Percentiles []map[string]float64 `json:"percentiles"` 12 | Topic string `json:"topic"` 13 | Channel string `json:"channel"` 14 | Addr string `json:"host"` 15 | } 16 | 17 | func (e *E2eProcessingLatencyAggregate) UnmarshalJSON(b []byte) error { 18 | var resp struct { 19 | Count int `json:"count"` 20 | Percentiles []map[string]float64 `json:"percentiles"` 21 | Topic string `json:"topic"` 22 | Channel string `json:"channel"` 23 | Addr string `json:"host"` 24 | } 25 | err := json.Unmarshal(b, &resp) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | for _, p := range resp.Percentiles { 31 | p["min"] = p["value"] 32 | p["max"] = p["value"] 33 | p["average"] = p["value"] 34 | p["count"] = float64(resp.Count) 35 | } 36 | 37 | e.Count = resp.Count 38 | e.Percentiles = resp.Percentiles 39 | e.Topic = resp.Topic 40 | e.Channel = resp.Channel 41 | e.Addr = resp.Addr 42 | 43 | return nil 44 | } 45 | 46 | func (e *E2eProcessingLatencyAggregate) Len() int { return len(e.Percentiles) } 47 | func (e *E2eProcessingLatencyAggregate) Swap(i, j int) { 48 | e.Percentiles[i], e.Percentiles[j] = e.Percentiles[j], e.Percentiles[i] 49 | } 50 | func (e *E2eProcessingLatencyAggregate) Less(i, j int) bool { 51 | return e.Percentiles[i]["percentile"] > e.Percentiles[j]["percentile"] 52 | } 53 | 54 | // Add merges e2 into e by averaging the percentiles 55 | func (e *E2eProcessingLatencyAggregate) Add(e2 *E2eProcessingLatencyAggregate) { 56 | e.Addr = "*" 57 | p := e.Percentiles 58 | e.Count += e2.Count 59 | for _, value := range e2.Percentiles { 60 | i := -1 61 | for j, v := range p { 62 | if value["quantile"] == v["quantile"] { 63 | i = j 64 | break 65 | } 66 | } 67 | if i == -1 { 68 | i = len(p) 69 | e.Percentiles = append(p, make(map[string]float64)) 70 | p = e.Percentiles 71 | p[i]["quantile"] = value["quantile"] 72 | } 73 | p[i]["max"] = math.Max(value["max"], p[i]["max"]) 74 | p[i]["min"] = math.Min(value["max"], p[i]["max"]) 75 | p[i]["count"] += value["count"] 76 | if p[i]["count"] == 0 { 77 | p[i]["average"] = 0 78 | continue 79 | } 80 | delta := value["average"] - p[i]["average"] 81 | R := delta * value["count"] / p[i]["count"] 82 | p[i]["average"] = p[i]["average"] + R 83 | } 84 | sort.Sort(e) 85 | } 86 | -------------------------------------------------------------------------------- /nsqd/rename_windows_test.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package nsqd 4 | 5 | import ( 6 | "fmt" 7 | "io/ioutil" 8 | "math/rand" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | "testing" 13 | "time" 14 | 15 | "github.com/nsqio/nsq/internal/util" 16 | ) 17 | 18 | const TEST_FILE_COUNT = 500 19 | 20 | func TestConcurrentRenames(t *testing.T) { 21 | var waitGroup util.WaitGroupWrapper 22 | 23 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 24 | trigger := make(chan struct{}) 25 | testDir := filepath.Join(os.TempDir(), fmt.Sprintf("nsqd_TestConcurrentRenames_%d", r.Int())) 26 | 27 | err := os.MkdirAll(testDir, 644) 28 | if err != nil { 29 | t.Error(err) 30 | } 31 | 32 | fis, err := ioutil.ReadDir(testDir) 33 | if err != nil { 34 | t.Error(err) 35 | } else if len(fis) > 0 { 36 | t.Errorf("Test directory %s unexpectedly has %d items in it!", testDir, len(fis)) 37 | t.FailNow() 38 | } 39 | 40 | // create a bunch of source files and attempt to concurrently rename them all 41 | for i := 1; i <= TEST_FILE_COUNT; i++ { 42 | //First rename doesn't overwrite/replace; no target present 43 | sourcePath1 := filepath.Join(testDir, fmt.Sprintf("source1_%d.txt", i)) 44 | //Second rename will replace 45 | sourcePath2 := filepath.Join(testDir, fmt.Sprintf("source2_%d.txt", i)) 46 | targetPath := filepath.Join(testDir, fmt.Sprintf("target_%d.txt", i)) 47 | err = ioutil.WriteFile(sourcePath1, []byte(sourcePath1), 0644) 48 | if err != nil { 49 | t.Error(err) 50 | } 51 | err = ioutil.WriteFile(sourcePath2, []byte(sourcePath2), 0644) 52 | if err != nil { 53 | t.Error(err) 54 | } 55 | 56 | waitGroup.Wrap(func() { 57 | _, _ = <-trigger 58 | err := atomicRename(sourcePath1, targetPath) 59 | if err != nil { 60 | t.Error(err) 61 | } 62 | err = atomicRename(sourcePath2, targetPath) 63 | if err != nil { 64 | t.Error(err) 65 | } 66 | }) 67 | } 68 | 69 | // start.. they're off to the races! 70 | close(trigger) 71 | 72 | // wait for completion... 73 | waitGroup.Wait() 74 | 75 | // no source files should exist any longer; we should just have 500 target files 76 | fis, err = ioutil.ReadDir(testDir) 77 | if err != nil { 78 | t.Error(err) 79 | } else if len(fis) != TEST_FILE_COUNT { 80 | t.Errorf("Test directory %s unexpectedly has %d items in it!", testDir, len(fis)) 81 | } else { 82 | for _, fi := range fis { 83 | if !strings.HasPrefix(fi.Name(), "target_") { 84 | t.Errorf("Test directory file %s is not expected target file!", fi.Name()) 85 | } 86 | } 87 | } 88 | 89 | // clean up the test directory 90 | err = os.RemoveAll(testDir) 91 | if err != nil { 92 | t.Error(err) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/lookup.hbs: -------------------------------------------------------------------------------- 1 | {{> warning}} 2 | {{> error}} 3 | 4 |
5 |
6 |

Lookup

7 |
8 |
9 | 10 | {{#unless nsqlookupd.length}} 11 |
12 |

Notice

nsqadmin is not configured with nsqlookupd hosts 13 |
14 | {{else}} 15 |
16 |
17 | 18 | 19 | 20 | 21 | {{#each nsqlookupd}} 22 | 23 | {{/each}} 24 |
nsqlookupd Host
{{this}}
25 |
26 |
27 | 28 |
29 |
30 | {{#if topics}} 31 |
32 | Below is a tree of Topics/Channels that are currently inactive (i.e. not produced on any nsqd in the cluster but are present in the lookup data) 33 |
34 |
    35 | {{#each topics}} 36 |
  • 37 | {{name}} 38 |
      39 | {{#each channels}} 40 |
    • 41 | {{this}} 42 |
    • 43 | {{/each}} 44 |
    45 |
  • 46 | {{/each}} 47 |
48 | {{else}} 49 |

Notice

No inactive Topics
50 | {{/if}} 51 |
52 |
53 | 54 |
55 |
56 |
57 | Create Topic/Channel 58 |
59 |

This provides a way to setup a stream hierarchy 60 | before services are deployed to production. 61 |

If Channel Name is empty, just the topic is created. 62 |

63 |
64 | 65 | 66 |
67 | 68 |
69 |
70 |
71 | {{/unless}} 72 | -------------------------------------------------------------------------------- /nsqd/stats_test.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "testing" 9 | "time" 10 | 11 | "github.com/mreiferson/go-snappystream" 12 | "github.com/nsqio/nsq/internal/http_api" 13 | "github.com/nsqio/nsq/internal/test" 14 | ) 15 | 16 | func TestStats(t *testing.T) { 17 | opts := NewOptions() 18 | opts.Logger = test.NewTestLogger(t) 19 | tcpAddr, _, nsqd := mustStartNSQD(opts) 20 | defer os.RemoveAll(opts.DataPath) 21 | defer nsqd.Exit() 22 | 23 | topicName := "test_stats" + strconv.Itoa(int(time.Now().Unix())) 24 | topic := nsqd.GetTopic(topicName) 25 | msg := NewMessage(<-nsqd.idChan, []byte("test body")) 26 | topic.PutMessage(msg) 27 | 28 | conn, err := mustConnectNSQD(tcpAddr) 29 | test.Nil(t, err) 30 | defer conn.Close() 31 | 32 | identify(t, conn, nil, frameTypeResponse) 33 | sub(t, conn, topicName, "ch") 34 | 35 | stats := nsqd.GetStats() 36 | t.Logf("stats: %+v", stats) 37 | 38 | test.Equal(t, 1, len(stats)) 39 | test.Equal(t, 1, len(stats[0].Channels)) 40 | test.Equal(t, 1, len(stats[0].Channels[0].Clients)) 41 | } 42 | 43 | func TestClientAttributes(t *testing.T) { 44 | userAgent := "Test User Agent" 45 | 46 | opts := NewOptions() 47 | opts.Logger = test.NewTestLogger(t) 48 | opts.Verbose = true 49 | opts.SnappyEnabled = true 50 | tcpAddr, httpAddr, nsqd := mustStartNSQD(opts) 51 | defer os.RemoveAll(opts.DataPath) 52 | defer nsqd.Exit() 53 | 54 | conn, err := mustConnectNSQD(tcpAddr) 55 | test.Nil(t, err) 56 | defer conn.Close() 57 | 58 | data := identify(t, conn, map[string]interface{}{ 59 | "snappy": true, 60 | "user_agent": userAgent, 61 | }, frameTypeResponse) 62 | resp := struct { 63 | Snappy bool `json:"snappy"` 64 | UserAgent string `json:"user_agent"` 65 | }{} 66 | err = json.Unmarshal(data, &resp) 67 | test.Nil(t, err) 68 | test.Equal(t, true, resp.Snappy) 69 | 70 | r := snappystream.NewReader(conn, snappystream.SkipVerifyChecksum) 71 | w := snappystream.NewWriter(conn) 72 | readValidate(t, r, frameTypeResponse, "OK") 73 | 74 | topicName := "test_client_attributes" + strconv.Itoa(int(time.Now().Unix())) 75 | sub(t, readWriter{r, w}, topicName, "ch") 76 | 77 | var d struct { 78 | Topics []struct { 79 | Channels []struct { 80 | Clients []struct { 81 | UserAgent string `json:"user_agent"` 82 | Snappy bool `json:"snappy"` 83 | } `json:"clients"` 84 | } `json:"channels"` 85 | } `json:"topics"` 86 | } 87 | 88 | endpoint := fmt.Sprintf("http://127.0.0.1:%d/stats?format=json", httpAddr.Port) 89 | err = http_api.NewClient(nil, ConnectTimeout, RequestTimeout).GETV1(endpoint, &d) 90 | test.Nil(t, err) 91 | 92 | test.Equal(t, userAgent, d.Topics[0].Channels[0].Clients[0].UserAgent) 93 | test.Equal(t, true, d.Topics[0].Channels[0].Clients[0].Snappy) 94 | } 95 | -------------------------------------------------------------------------------- /nsqd/message.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "io" 8 | "time" 9 | ) 10 | 11 | const ( 12 | MsgIDLength = 16 13 | minValidMsgLength = MsgIDLength + 8 + 2 // Timestamp + Attempts 14 | ) 15 | 16 | type MessageID [MsgIDLength]byte 17 | 18 | type Message struct { 19 | ID MessageID 20 | Body []byte 21 | Timestamp int64 22 | Attempts uint16 23 | 24 | // for in-flight handling 25 | deliveryTS time.Time 26 | clientID int64 27 | pri int64 28 | index int 29 | deferred time.Duration 30 | } 31 | 32 | func NewMessage(id MessageID, body []byte) *Message { 33 | return &Message{ 34 | ID: id, 35 | Body: body, 36 | Timestamp: time.Now().UnixNano(), 37 | } 38 | } 39 | 40 | func (m *Message) WriteTo(w io.Writer) (int64, error) { 41 | var buf [10]byte 42 | var total int64 43 | 44 | binary.BigEndian.PutUint64(buf[:8], uint64(m.Timestamp)) 45 | binary.BigEndian.PutUint16(buf[8:10], uint16(m.Attempts)) 46 | 47 | n, err := w.Write(buf[:]) 48 | total += int64(n) 49 | if err != nil { 50 | return total, err 51 | } 52 | 53 | n, err = w.Write(m.ID[:]) 54 | total += int64(n) 55 | if err != nil { 56 | return total, err 57 | } 58 | 59 | n, err = w.Write(m.Body) 60 | total += int64(n) 61 | if err != nil { 62 | return total, err 63 | } 64 | 65 | return total, nil 66 | } 67 | 68 | // decodeMessage deserializes data (as []byte) and creates a new Message 69 | // message format: 70 | // [x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x]... 71 | // | (int64) || || (hex string encoded in ASCII) || (binary) 72 | // | 8-byte || || 16-byte || N-byte 73 | // ------------------------------------------------------------------------------------------... 74 | // nanosecond timestamp ^^ message ID message body 75 | // (uint16) 76 | // 2-byte 77 | // attempts 78 | func decodeMessage(b []byte) (*Message, error) { 79 | var msg Message 80 | 81 | if len(b) < minValidMsgLength { 82 | return nil, fmt.Errorf("invalid message buffer size (%d)", len(b)) 83 | } 84 | 85 | msg.Timestamp = int64(binary.BigEndian.Uint64(b[:8])) 86 | msg.Attempts = binary.BigEndian.Uint16(b[8:10]) 87 | copy(msg.ID[:], b[10:10+MsgIDLength]) 88 | msg.Body = b[10+MsgIDLength:] 89 | 90 | return &msg, nil 91 | } 92 | 93 | func writeMessageToBackend(buf *bytes.Buffer, msg *Message, bq BackendQueue) error { 94 | buf.Reset() 95 | _, err := msg.WriteTo(buf) 96 | if err != nil { 97 | return err 98 | } 99 | return bq.Put(buf.Bytes()) 100 | } 101 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/lookup.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | var $ = require('jquery'); 3 | 4 | var AppState = require('../app_state'); 5 | var Pubsub = require('../lib/pubsub'); 6 | var BaseView = require('./base'); 7 | 8 | var Topic = require('../models/topic'); 9 | var Channel = require('../models/channel'); 10 | 11 | var LookupView = BaseView.extend({ 12 | className: 'lookup container-fluid', 13 | 14 | template: require('./spinner.hbs'), 15 | 16 | events: { 17 | 'click .hierarchy button': 'onCreateTopicChannel', 18 | 'click .delete-topic-link': 'onDeleteTopic', 19 | 'click .delete-channel-link': 'onDeleteChannel' 20 | }, 21 | 22 | initialize: function() { 23 | BaseView.prototype.initialize.apply(this, arguments); 24 | $.ajax(AppState.url('/topics?inactive=true')) 25 | .done(function(data) { 26 | this.template = require('./lookup.hbs'); 27 | this.render({ 28 | 'topics': _.map(data['topics'], function(v, k) { 29 | return {'name': k, 'channels': v}; 30 | }), 31 | 'message': data['message'] 32 | }); 33 | }.bind(this)) 34 | .fail(this.handleViewError.bind(this)) 35 | .always(Pubsub.trigger.bind(Pubsub, 'view:ready')); 36 | }, 37 | 38 | onCreateTopicChannel: function(e) { 39 | e.preventDefault(); 40 | e.stopPropagation(); 41 | var topic = $(e.target.form.elements['topic']).val(); 42 | var channel = $(e.target.form.elements['channel']).val(); 43 | if (topic === '' && channel === '') { 44 | return; 45 | } 46 | $.post(AppState.url('/topics'), JSON.stringify({ 47 | 'topic': topic, 48 | 'channel': channel 49 | })) 50 | .done(function() { window.location.reload(true); }) 51 | .fail(this.handleAJAXError.bind(this)); 52 | }, 53 | 54 | onDeleteTopic: function(e) { 55 | e.preventDefault(); 56 | e.stopPropagation(); 57 | var topic = new Topic({ 58 | 'name': $(e.target).data('topic') 59 | }); 60 | topic.destroy({'dataType': 'text'}) 61 | .done(function() { window.location.reload(true); }) 62 | .fail(this.handleAJAXError.bind(this)); 63 | }, 64 | 65 | onDeleteChannel: function(e) { 66 | e.preventDefault(); 67 | e.stopPropagation(); 68 | var channel = new Channel({ 69 | 'topic': $(e.target).data('topic'), 70 | 'name': $(e.target).data('channel') 71 | }); 72 | channel.destroy({'dataType': 'text'}) 73 | .done(function() { window.location.reload(true); }) 74 | .fail(this.handleAJAXError.bind(this)); 75 | } 76 | }); 77 | 78 | module.exports = LookupView; 79 | -------------------------------------------------------------------------------- /bench/bench_writer/bench_writer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "log" 7 | "net" 8 | "runtime" 9 | "sync" 10 | "sync/atomic" 11 | "time" 12 | 13 | "github.com/nsqio/go-nsq" 14 | ) 15 | 16 | var ( 17 | runfor = flag.Duration("runfor", 10*time.Second, "duration of time to run") 18 | tcpAddress = flag.String("nsqd-tcp-address", "127.0.0.1:4150", ": to connect to nsqd") 19 | topic = flag.String("topic", "sub_bench", "topic to receive messages on") 20 | size = flag.Int("size", 200, "size of messages") 21 | batchSize = flag.Int("batch-size", 200, "batch size of messages") 22 | deadline = flag.String("deadline", "", "deadline to start the benchmark run") 23 | ) 24 | 25 | var totalMsgCount int64 26 | 27 | func main() { 28 | flag.Parse() 29 | var wg sync.WaitGroup 30 | 31 | log.SetPrefix("[bench_writer] ") 32 | 33 | msg := make([]byte, *size) 34 | batch := make([][]byte, *batchSize) 35 | for i := range batch { 36 | batch[i] = msg 37 | } 38 | 39 | goChan := make(chan int) 40 | rdyChan := make(chan int) 41 | for j := 0; j < runtime.GOMAXPROCS(0); j++ { 42 | wg.Add(1) 43 | go func() { 44 | pubWorker(*runfor, *tcpAddress, *batchSize, batch, *topic, rdyChan, goChan) 45 | wg.Done() 46 | }() 47 | <-rdyChan 48 | } 49 | 50 | if *deadline != "" { 51 | t, err := time.Parse("2006-01-02 15:04:05", *deadline) 52 | if err != nil { 53 | log.Fatal(err) 54 | } 55 | d := t.Sub(time.Now()) 56 | log.Printf("sleeping until %s (%s)", t, d) 57 | time.Sleep(d) 58 | } 59 | 60 | start := time.Now() 61 | close(goChan) 62 | wg.Wait() 63 | end := time.Now() 64 | duration := end.Sub(start) 65 | tmc := atomic.LoadInt64(&totalMsgCount) 66 | log.Printf("duration: %s - %.03fmb/s - %.03fops/s - %.03fus/op", 67 | duration, 68 | float64(tmc*int64(*size))/duration.Seconds()/1024/1024, 69 | float64(tmc)/duration.Seconds(), 70 | float64(duration/time.Microsecond)/float64(tmc)) 71 | } 72 | 73 | func pubWorker(td time.Duration, tcpAddr string, batchSize int, batch [][]byte, topic string, rdyChan chan int, goChan chan int) { 74 | conn, err := net.DialTimeout("tcp", tcpAddr, time.Second) 75 | if err != nil { 76 | panic(err.Error()) 77 | } 78 | conn.Write(nsq.MagicV2) 79 | rw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) 80 | rdyChan <- 1 81 | <-goChan 82 | var msgCount int64 83 | endTime := time.Now().Add(td) 84 | for { 85 | cmd, _ := nsq.MultiPublish(topic, batch) 86 | _, err := cmd.WriteTo(rw) 87 | if err != nil { 88 | panic(err.Error()) 89 | } 90 | err = rw.Flush() 91 | if err != nil { 92 | panic(err.Error()) 93 | } 94 | resp, err := nsq.ReadResponse(rw) 95 | if err != nil { 96 | panic(err.Error()) 97 | } 98 | frameType, data, err := nsq.UnpackResponse(resp) 99 | if err != nil { 100 | panic(err.Error()) 101 | } 102 | if frameType == nsq.FrameTypeError { 103 | panic(string(data)) 104 | } 105 | msgCount += int64(len(batch)) 106 | if time.Now().After(endTime) { 107 | break 108 | } 109 | } 110 | atomic.AddInt64(&totalMsgCount, msgCount) 111 | } 112 | -------------------------------------------------------------------------------- /internal/quantile/quantile.go: -------------------------------------------------------------------------------- 1 | package quantile 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | "time" 7 | 8 | "github.com/bmizerany/perks/quantile" 9 | "github.com/nsqio/nsq/internal/stringy" 10 | ) 11 | 12 | type Result struct { 13 | Count int `json:"count"` 14 | Percentiles []map[string]float64 `json:"percentiles"` 15 | } 16 | 17 | func (r *Result) String() string { 18 | var s []string 19 | for _, item := range r.Percentiles { 20 | s = append(s, stringy.NanoSecondToHuman(item["value"])) 21 | } 22 | return strings.Join(s, ", ") 23 | } 24 | 25 | type Quantile struct { 26 | sync.Mutex 27 | streams [2]quantile.Stream 28 | currentIndex uint8 29 | lastMoveWindow time.Time 30 | currentStream *quantile.Stream 31 | 32 | Percentiles []float64 33 | MoveWindowTime time.Duration 34 | } 35 | 36 | func New(WindowTime time.Duration, Percentiles []float64) *Quantile { 37 | q := Quantile{ 38 | currentIndex: 0, 39 | lastMoveWindow: time.Now(), 40 | MoveWindowTime: WindowTime / 2, 41 | Percentiles: Percentiles, 42 | } 43 | for i := 0; i < 2; i++ { 44 | q.streams[i] = *quantile.NewTargeted(Percentiles...) 45 | } 46 | q.currentStream = &q.streams[0] 47 | return &q 48 | } 49 | 50 | func (q *Quantile) Result() *Result { 51 | if q == nil { 52 | return &Result{} 53 | } 54 | queryHandler := q.QueryHandler() 55 | result := Result{ 56 | Count: queryHandler.Count(), 57 | Percentiles: make([]map[string]float64, len(q.Percentiles)), 58 | } 59 | for i, p := range q.Percentiles { 60 | value := queryHandler.Query(p) 61 | result.Percentiles[i] = map[string]float64{"quantile": p, "value": value} 62 | } 63 | return &result 64 | } 65 | 66 | func (q *Quantile) Insert(msgStartTime int64) { 67 | q.Lock() 68 | 69 | now := time.Now() 70 | for q.IsDataStale(now) { 71 | q.moveWindow() 72 | } 73 | 74 | q.currentStream.Insert(float64(now.UnixNano() - msgStartTime)) 75 | q.Unlock() 76 | } 77 | 78 | func (q *Quantile) QueryHandler() *quantile.Stream { 79 | q.Lock() 80 | now := time.Now() 81 | for q.IsDataStale(now) { 82 | q.moveWindow() 83 | } 84 | 85 | merged := quantile.NewTargeted(q.Percentiles...) 86 | merged.Merge(q.streams[0].Samples()) 87 | merged.Merge(q.streams[1].Samples()) 88 | q.Unlock() 89 | return merged 90 | } 91 | 92 | func (q *Quantile) IsDataStale(now time.Time) bool { 93 | return now.After(q.lastMoveWindow.Add(q.MoveWindowTime)) 94 | } 95 | 96 | func (q *Quantile) Merge(them *Quantile) { 97 | q.Lock() 98 | them.Lock() 99 | iUs := q.currentIndex 100 | iThem := them.currentIndex 101 | 102 | q.streams[iUs].Merge(them.streams[iThem].Samples()) 103 | 104 | iUs ^= 0x1 105 | iThem ^= 0x1 106 | q.streams[iUs].Merge(them.streams[iThem].Samples()) 107 | 108 | if q.lastMoveWindow.Before(them.lastMoveWindow) { 109 | q.lastMoveWindow = them.lastMoveWindow 110 | } 111 | q.Unlock() 112 | them.Unlock() 113 | } 114 | 115 | func (q *Quantile) moveWindow() { 116 | q.currentIndex ^= 0x1 117 | q.currentStream = &q.streams[q.currentIndex] 118 | q.lastMoveWindow = q.lastMoveWindow.Add(q.MoveWindowTime) 119 | q.currentStream.Reset() 120 | } 121 | -------------------------------------------------------------------------------- /nsqadmin/nsqadmin_test.go: -------------------------------------------------------------------------------- 1 | package nsqadmin 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net" 7 | "net/http" 8 | "net/url" 9 | "os" 10 | "os/exec" 11 | "testing" 12 | "time" 13 | 14 | "github.com/nsqio/nsq/internal/test" 15 | "github.com/nsqio/nsq/nsqd" 16 | ) 17 | 18 | func TestNoLogger(t *testing.T) { 19 | opts := NewOptions() 20 | opts.Logger = nil 21 | opts.HTTPAddress = "127.0.0.1:0" 22 | opts.NSQLookupdHTTPAddresses = []string{"127.0.0.1:4161"} 23 | nsqlookupd := New(opts) 24 | 25 | nsqlookupd.logf("should never be logged") 26 | } 27 | 28 | func TestNeitherNSQDAndNSQLookup(t *testing.T) { 29 | if os.Getenv("BE_CRASHER") == "1" { 30 | opts := NewOptions() 31 | opts.Logger = nil 32 | opts.HTTPAddress = "127.0.0.1:0" 33 | New(opts) 34 | return 35 | } 36 | cmd := exec.Command(os.Args[0], "-test.run=TestNeitherNSQDAndNSQLookup") 37 | cmd.Env = append(os.Environ(), "BE_CRASHER=1") 38 | err := cmd.Run() 39 | test.Equal(t, "exit status 1", fmt.Sprintf("%v", err)) 40 | if e, ok := err.(*exec.ExitError); ok && !e.Success() { 41 | return 42 | } 43 | t.Fatalf("process ran with err %v, want exit status 1", err) 44 | } 45 | 46 | func TestBothNSQDAndNSQLookup(t *testing.T) { 47 | if os.Getenv("BE_CRASHER") == "1" { 48 | opts := NewOptions() 49 | opts.Logger = nil 50 | opts.HTTPAddress = "127.0.0.1:0" 51 | opts.NSQLookupdHTTPAddresses = []string{"127.0.0.1:4161"} 52 | opts.NSQDHTTPAddresses = []string{"127.0.0.1:4151"} 53 | New(opts) 54 | return 55 | } 56 | cmd := exec.Command(os.Args[0], "-test.run=TestBothNSQDAndNSQLookup") 57 | cmd.Env = append(os.Environ(), "BE_CRASHER=1") 58 | err := cmd.Run() 59 | test.Equal(t, "exit status 1", fmt.Sprintf("%v", err)) 60 | if e, ok := err.(*exec.ExitError); ok && !e.Success() { 61 | return 62 | } 63 | t.Fatalf("process ran with err %v, want exit status 1", err) 64 | } 65 | 66 | func TestTLSHTTPClient(t *testing.T) { 67 | nsqdOpts := nsqd.NewOptions() 68 | nsqdOpts.Verbose = true 69 | nsqdOpts.TLSCert = "./test/server.pem" 70 | nsqdOpts.TLSKey = "./test/server-key.pem" 71 | nsqdOpts.TLSRootCAFile = "./test/ca.pem" 72 | nsqdOpts.TLSClientAuthPolicy = "require-verify" 73 | _, nsqdHTTPAddr, nsqd := mustStartNSQD(nsqdOpts) 74 | defer os.RemoveAll(nsqdOpts.DataPath) 75 | defer nsqd.Exit() 76 | 77 | opts := NewOptions() 78 | opts.HTTPAddress = "127.0.0.1:0" 79 | opts.NSQDHTTPAddresses = []string{nsqdHTTPAddr.String()} 80 | opts.HTTPClientTLSRootCAFile = "./test/ca.pem" 81 | opts.HTTPClientTLSCert = "./test/client.pem" 82 | opts.HTTPClientTLSKey = "./test/client-key.pem" 83 | nsqadmin := New(opts) 84 | nsqadmin.Main() 85 | defer nsqadmin.Exit() 86 | 87 | httpAddr := nsqadmin.RealHTTPAddr() 88 | u := url.URL{ 89 | Scheme: "http", 90 | Host: httpAddr.String(), 91 | Path: "/api/nodes/" + nsqdHTTPAddr.String(), 92 | } 93 | 94 | resp, err := http.Get(u.String()) 95 | defer resp.Body.Close() 96 | 97 | test.Equal(t, nil, err) 98 | test.Equal(t, resp.StatusCode < 500, true) 99 | } 100 | 101 | func mustStartNSQD(opts *nsqd.Options) (*net.TCPAddr, *net.TCPAddr, *nsqd.NSQD) { 102 | opts.TCPAddress = "127.0.0.1:0" 103 | opts.HTTPAddress = "127.0.0.1:0" 104 | opts.HTTPSAddress = "127.0.0.1:0" 105 | if opts.DataPath == "" { 106 | tmpDir, err := ioutil.TempDir("", fmt.Sprintf("nsq-test-%d", time.Now().UnixNano())) 107 | if err != nil { 108 | panic(err) 109 | } 110 | opts.DataPath = tmpDir 111 | } 112 | nsqd := nsqd.New(opts) 113 | nsqd.Main() 114 | return nsqd.RealTCPAddr(), nsqd.RealHTTPAddr(), nsqd 115 | } 116 | -------------------------------------------------------------------------------- /bench/bench_reader/bench_reader.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "log" 7 | "net" 8 | "runtime" 9 | "strings" 10 | "sync" 11 | "sync/atomic" 12 | "time" 13 | 14 | "github.com/nsqio/go-nsq" 15 | ) 16 | 17 | var ( 18 | runfor = flag.Duration("runfor", 10*time.Second, "duration of time to run") 19 | tcpAddress = flag.String("nsqd-tcp-address", "127.0.0.1:4150", ": to connect to nsqd") 20 | size = flag.Int("size", 200, "size of messages") 21 | topic = flag.String("topic", "sub_bench", "topic to receive messages on") 22 | channel = flag.String("channel", "ch", "channel to receive messages on") 23 | deadline = flag.String("deadline", "", "deadline to start the benchmark run") 24 | rdy = flag.Int("rdy", 2500, "RDY count to use") 25 | ) 26 | 27 | var totalMsgCount int64 28 | 29 | func main() { 30 | flag.Parse() 31 | var wg sync.WaitGroup 32 | 33 | log.SetPrefix("[bench_reader] ") 34 | 35 | goChan := make(chan int) 36 | rdyChan := make(chan int) 37 | workers := runtime.GOMAXPROCS(0) 38 | for j := 0; j < workers; j++ { 39 | wg.Add(1) 40 | go func(id int) { 41 | subWorker(*runfor, workers, *tcpAddress, *topic, *channel, rdyChan, goChan, id) 42 | wg.Done() 43 | }(j) 44 | <-rdyChan 45 | } 46 | 47 | if *deadline != "" { 48 | t, err := time.Parse("2006-01-02 15:04:05", *deadline) 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | d := t.Sub(time.Now()) 53 | log.Printf("sleeping until %s (%s)", t, d) 54 | time.Sleep(d) 55 | } 56 | 57 | start := time.Now() 58 | close(goChan) 59 | wg.Wait() 60 | end := time.Now() 61 | duration := end.Sub(start) 62 | tmc := atomic.LoadInt64(&totalMsgCount) 63 | log.Printf("duration: %s - %.03fmb/s - %.03fops/s - %.03fus/op", 64 | duration, 65 | float64(tmc*int64(*size))/duration.Seconds()/1024/1024, 66 | float64(tmc)/duration.Seconds(), 67 | float64(duration/time.Microsecond)/float64(tmc)) 68 | } 69 | 70 | func subWorker(td time.Duration, workers int, tcpAddr string, topic string, channel string, rdyChan chan int, goChan chan int, id int) { 71 | conn, err := net.DialTimeout("tcp", tcpAddr, time.Second) 72 | if err != nil { 73 | panic(err.Error()) 74 | } 75 | conn.Write(nsq.MagicV2) 76 | rw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) 77 | ci := make(map[string]interface{}) 78 | ci["short_id"] = "test" 79 | ci["long_id"] = "test" 80 | cmd, _ := nsq.Identify(ci) 81 | cmd.WriteTo(rw) 82 | nsq.Subscribe(topic, channel).WriteTo(rw) 83 | rdyChan <- 1 84 | <-goChan 85 | nsq.Ready(*rdy).WriteTo(rw) 86 | rw.Flush() 87 | nsq.ReadResponse(rw) 88 | nsq.ReadResponse(rw) 89 | var msgCount int64 90 | go func() { 91 | time.Sleep(td) 92 | conn.Close() 93 | }() 94 | for { 95 | resp, err := nsq.ReadResponse(rw) 96 | if err != nil { 97 | if strings.Contains(err.Error(), "use of closed network connection") { 98 | break 99 | } 100 | panic(err.Error()) 101 | } 102 | frameType, data, err := nsq.UnpackResponse(resp) 103 | if err != nil { 104 | panic(err.Error()) 105 | } 106 | if frameType == nsq.FrameTypeError { 107 | panic(string(data)) 108 | } else if frameType == nsq.FrameTypeResponse { 109 | continue 110 | } 111 | msg, err := nsq.DecodeMessage(data) 112 | if err != nil { 113 | panic(err.Error()) 114 | } 115 | nsq.Finish(msg.ID).WriteTo(rw) 116 | msgCount++ 117 | if float64(msgCount%int64(*rdy)) > float64(*rdy)*0.75 { 118 | rw.Flush() 119 | } 120 | } 121 | atomic.AddInt64(&totalMsgCount, msgCount) 122 | } 123 | -------------------------------------------------------------------------------- /apps/nsq_tail/nsq_tail.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "math/rand" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | 13 | "github.com/nsqio/go-nsq" 14 | "github.com/nsqio/nsq/internal/app" 15 | "github.com/nsqio/nsq/internal/version" 16 | ) 17 | 18 | var ( 19 | showVersion = flag.Bool("version", false, "print version string") 20 | 21 | topic = flag.String("topic", "", "NSQ topic") 22 | channel = flag.String("channel", "", "NSQ channel") 23 | maxInFlight = flag.Int("max-in-flight", 200, "max number of messages to allow in flight") 24 | totalMessages = flag.Int("n", 0, "total messages to show (will wait if starved)") 25 | 26 | nsqdTCPAddrs = app.StringArray{} 27 | lookupdHTTPAddrs = app.StringArray{} 28 | ) 29 | 30 | func init() { 31 | flag.Var(&nsqdTCPAddrs, "nsqd-tcp-address", "nsqd TCP address (may be given multiple times)") 32 | flag.Var(&lookupdHTTPAddrs, "lookupd-http-address", "lookupd HTTP address (may be given multiple times)") 33 | } 34 | 35 | type TailHandler struct { 36 | totalMessages int 37 | messagesShown int 38 | } 39 | 40 | func (th *TailHandler) HandleMessage(m *nsq.Message) error { 41 | th.messagesShown++ 42 | _, err := os.Stdout.Write(m.Body) 43 | if err != nil { 44 | log.Fatalf("ERROR: failed to write to os.Stdout - %s", err) 45 | } 46 | _, err = os.Stdout.WriteString("\n") 47 | if err != nil { 48 | log.Fatalf("ERROR: failed to write to os.Stdout - %s", err) 49 | } 50 | if th.totalMessages > 0 && th.messagesShown >= th.totalMessages { 51 | os.Exit(0) 52 | } 53 | return nil 54 | } 55 | 56 | func main() { 57 | cfg := nsq.NewConfig() 58 | // TODO: remove, deprecated 59 | flag.Var(&nsq.ConfigFlag{cfg}, "reader-opt", "(deprecated) use --consumer-opt") 60 | flag.Var(&nsq.ConfigFlag{cfg}, "consumer-opt", "option to passthrough to nsq.Consumer (may be given multiple times, http://godoc.org/github.com/nsqio/go-nsq#Config)") 61 | 62 | flag.Parse() 63 | 64 | if *showVersion { 65 | fmt.Printf("nsq_tail v%s\n", version.Binary) 66 | return 67 | } 68 | 69 | if *channel == "" { 70 | rand.Seed(time.Now().UnixNano()) 71 | *channel = fmt.Sprintf("tail%06d#ephemeral", rand.Int()%999999) 72 | } 73 | 74 | if *topic == "" { 75 | log.Fatal("--topic is required") 76 | } 77 | 78 | if len(nsqdTCPAddrs) == 0 && len(lookupdHTTPAddrs) == 0 { 79 | log.Fatal("--nsqd-tcp-address or --lookupd-http-address required") 80 | } 81 | if len(nsqdTCPAddrs) > 0 && len(lookupdHTTPAddrs) > 0 { 82 | log.Fatal("use --nsqd-tcp-address or --lookupd-http-address not both") 83 | } 84 | 85 | sigChan := make(chan os.Signal, 1) 86 | signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) 87 | 88 | // Don't ask for more messages than we want 89 | if *totalMessages > 0 && *totalMessages < *maxInFlight { 90 | *maxInFlight = *totalMessages 91 | } 92 | 93 | cfg.UserAgent = fmt.Sprintf("nsq_tail/%s go-nsq/%s", version.Binary, nsq.VERSION) 94 | cfg.MaxInFlight = *maxInFlight 95 | 96 | consumer, err := nsq.NewConsumer(*topic, *channel, cfg) 97 | if err != nil { 98 | log.Fatal(err) 99 | } 100 | 101 | consumer.AddHandler(&TailHandler{totalMessages: *totalMessages}) 102 | 103 | err = consumer.ConnectToNSQDs(nsqdTCPAddrs) 104 | if err != nil { 105 | log.Fatal(err) 106 | } 107 | 108 | err = consumer.ConnectToNSQLookupds(lookupdHTTPAddrs) 109 | if err != nil { 110 | log.Fatal(err) 111 | } 112 | 113 | for { 114 | select { 115 | case <-consumer.StopChan: 116 | return 117 | case <-sigChan: 118 | consumer.Stop() 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /nsqlookupd/registration_db_test.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/nsqio/nsq/internal/test" 8 | ) 9 | 10 | func TestRegistrationDB(t *testing.T) { 11 | sec30 := 30 * time.Second 12 | beginningOfTime := time.Unix(1348797047, 0) 13 | pi1 := &PeerInfo{beginningOfTime.UnixNano(), "1", "remote_addr:1", "host", "b_addr", 1, 2, "v1"} 14 | pi2 := &PeerInfo{beginningOfTime.UnixNano(), "2", "remote_addr:2", "host", "b_addr", 2, 3, "v1"} 15 | pi3 := &PeerInfo{beginningOfTime.UnixNano(), "3", "remote_addr:3", "host", "b_addr", 3, 4, "v1"} 16 | p1 := &Producer{pi1, false, beginningOfTime} 17 | p2 := &Producer{pi2, false, beginningOfTime} 18 | p3 := &Producer{pi3, false, beginningOfTime} 19 | p4 := &Producer{pi1, false, beginningOfTime} 20 | 21 | db := NewRegistrationDB() 22 | 23 | // add producers 24 | db.AddProducer(Registration{"c", "a", ""}, p1) 25 | db.AddProducer(Registration{"c", "a", ""}, p2) 26 | db.AddProducer(Registration{"c", "a", "b"}, p2) 27 | db.AddProducer(Registration{"d", "a", ""}, p3) 28 | db.AddProducer(Registration{"t", "a", ""}, p4) 29 | 30 | // find producers 31 | r := db.FindRegistrations("c", "*", "").Keys() 32 | test.Equal(t, 1, len(r)) 33 | test.Equal(t, "a", r[0]) 34 | 35 | p := db.FindProducers("t", "*", "") 36 | t.Logf("%s", p) 37 | test.Equal(t, 1, len(p)) 38 | p = db.FindProducers("c", "*", "") 39 | t.Logf("%s", p) 40 | test.Equal(t, 2, len(p)) 41 | p = db.FindProducers("c", "a", "") 42 | t.Logf("%s", p) 43 | test.Equal(t, 2, len(p)) 44 | p = db.FindProducers("c", "*", "b") 45 | t.Logf("%s", p) 46 | test.Equal(t, 1, len(p)) 47 | test.Equal(t, p2.peerInfo.id, p[0].peerInfo.id) 48 | 49 | // filter by active 50 | test.Equal(t, 0, len(p.FilterByActive(sec30, sec30))) 51 | p2.peerInfo.lastUpdate = time.Now().UnixNano() 52 | test.Equal(t, 1, len(p.FilterByActive(sec30, sec30))) 53 | p = db.FindProducers("c", "*", "") 54 | t.Logf("%s", p) 55 | test.Equal(t, 1, len(p.FilterByActive(sec30, sec30))) 56 | 57 | // tombstoning 58 | fewSecAgo := time.Now().Add(-5 * time.Second).UnixNano() 59 | p1.peerInfo.lastUpdate = fewSecAgo 60 | p2.peerInfo.lastUpdate = fewSecAgo 61 | test.Equal(t, 2, len(p.FilterByActive(sec30, sec30))) 62 | p1.Tombstone() 63 | test.Equal(t, 1, len(p.FilterByActive(sec30, sec30))) 64 | time.Sleep(10 * time.Millisecond) 65 | test.Equal(t, 2, len(p.FilterByActive(sec30, 5*time.Millisecond))) 66 | // make sure we can still retrieve p1 from another registration see #148 67 | test.Equal(t, 1, len(db.FindProducers("t", "*", "").FilterByActive(sec30, sec30))) 68 | 69 | // keys and subkeys 70 | k := db.FindRegistrations("c", "b", "").Keys() 71 | test.Equal(t, 0, len(k)) 72 | k = db.FindRegistrations("c", "a", "").Keys() 73 | test.Equal(t, 1, len(k)) 74 | test.Equal(t, "a", k[0]) 75 | k = db.FindRegistrations("c", "*", "b").SubKeys() 76 | test.Equal(t, 1, len(k)) 77 | test.Equal(t, "b", k[0]) 78 | 79 | // removing producers 80 | db.RemoveProducer(Registration{"c", "a", ""}, p1.peerInfo.id) 81 | p = db.FindProducers("c", "*", "*") 82 | t.Logf("%s", p) 83 | test.Equal(t, 1, len(p)) 84 | 85 | db.RemoveProducer(Registration{"c", "a", ""}, p2.peerInfo.id) 86 | db.RemoveProducer(Registration{"c", "a", "b"}, p2.peerInfo.id) 87 | p = db.FindProducers("c", "*", "*") 88 | t.Logf("%s", p) 89 | test.Equal(t, 0, len(p)) 90 | 91 | // do some key removals 92 | k = db.FindRegistrations("c", "*", "*").Keys() 93 | test.Equal(t, 2, len(k)) 94 | db.RemoveRegistration(Registration{"c", "a", ""}) 95 | db.RemoveRegistration(Registration{"c", "a", "b"}) 96 | k = db.FindRegistrations("c", "*", "*").Keys() 97 | test.Equal(t, 0, len(k)) 98 | } 99 | -------------------------------------------------------------------------------- /internal/auth/authorizations.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "net/url" 8 | "regexp" 9 | "time" 10 | 11 | "github.com/nsqio/nsq/internal/http_api" 12 | ) 13 | 14 | type Authorization struct { 15 | Topic string `json:"topic"` 16 | Channels []string `json:"channels"` 17 | Permissions []string `json:"permissions"` 18 | } 19 | 20 | type State struct { 21 | TTL int `json:"ttl"` 22 | Authorizations []Authorization `json:"authorizations"` 23 | Identity string `json:"identity"` 24 | IdentityURL string `json:"identity_url"` 25 | Expires time.Time 26 | } 27 | 28 | func (a *Authorization) HasPermission(permission string) bool { 29 | for _, p := range a.Permissions { 30 | if permission == p { 31 | return true 32 | } 33 | } 34 | return false 35 | } 36 | 37 | func (a *Authorization) IsAllowed(topic, channel string) bool { 38 | if channel != "" { 39 | if !a.HasPermission("subscribe") { 40 | return false 41 | } 42 | } else { 43 | if !a.HasPermission("publish") { 44 | return false 45 | } 46 | } 47 | 48 | topicRegex := regexp.MustCompile(a.Topic) 49 | 50 | if !topicRegex.MatchString(topic) { 51 | return false 52 | } 53 | 54 | for _, c := range a.Channels { 55 | channelRegex := regexp.MustCompile(c) 56 | if channelRegex.MatchString(channel) { 57 | return true 58 | } 59 | } 60 | return false 61 | } 62 | 63 | func (a *State) IsAllowed(topic, channel string) bool { 64 | for _, aa := range a.Authorizations { 65 | if aa.IsAllowed(topic, channel) { 66 | return true 67 | } 68 | } 69 | return false 70 | } 71 | 72 | func (a *State) IsExpired() bool { 73 | if a.Expires.Before(time.Now()) { 74 | return true 75 | } 76 | return false 77 | } 78 | 79 | func QueryAnyAuthd(authd []string, remoteIP, tlsEnabled, authSecret string, 80 | connectTimeout time.Duration, requestTimeout time.Duration) (*State, error) { 81 | for _, a := range authd { 82 | authState, err := QueryAuthd(a, remoteIP, tlsEnabled, authSecret, connectTimeout, requestTimeout) 83 | if err != nil { 84 | log.Printf("Error: failed auth against %s %s", a, err) 85 | continue 86 | } 87 | return authState, nil 88 | } 89 | return nil, errors.New("Unable to access auth server") 90 | } 91 | 92 | func QueryAuthd(authd, remoteIP, tlsEnabled, authSecret string, 93 | connectTimeout time.Duration, requestTimeout time.Duration) (*State, error) { 94 | v := url.Values{} 95 | v.Set("remote_ip", remoteIP) 96 | v.Set("tls", tlsEnabled) 97 | v.Set("secret", authSecret) 98 | 99 | endpoint := fmt.Sprintf("http://%s/auth?%s", authd, v.Encode()) 100 | 101 | var authState State 102 | client := http_api.NewClient(nil, connectTimeout, requestTimeout) 103 | if err := client.GETV1(endpoint, &authState); err != nil { 104 | return nil, err 105 | } 106 | 107 | // validation on response 108 | for _, auth := range authState.Authorizations { 109 | for _, p := range auth.Permissions { 110 | switch p { 111 | case "subscribe", "publish": 112 | default: 113 | return nil, fmt.Errorf("unknown permission %s", p) 114 | } 115 | } 116 | 117 | if _, err := regexp.Compile(auth.Topic); err != nil { 118 | return nil, fmt.Errorf("unable to compile topic %q %s", auth.Topic, err) 119 | } 120 | 121 | for _, channel := range auth.Channels { 122 | if _, err := regexp.Compile(channel); err != nil { 123 | return nil, fmt.Errorf("unable to compile channel %q %s", channel, err) 124 | } 125 | } 126 | } 127 | 128 | if authState.TTL <= 0 { 129 | return nil, fmt.Errorf("invalid TTL %d (must be >0)", authState.TTL) 130 | } 131 | 132 | authState.Expires = time.Now().Add(time.Duration(authState.TTL) * time.Second) 133 | return &authState, nil 134 | } 135 | -------------------------------------------------------------------------------- /contrib/nsqd.cfg.example: -------------------------------------------------------------------------------- 1 | ## enable verbose logging 2 | verbose = false 3 | 4 | ## unique identifier (int) for this worker (will default to a hash of hostname) 5 | # id = 5150 6 | 7 | ## : to listen on for TCP clients 8 | tcp_address = "0.0.0.0:4150" 9 | 10 | ## : to listen on for HTTP clients 11 | http_address = "0.0.0.0:4151" 12 | 13 | ## : to listen on for HTTPS clients 14 | # https_address = "0.0.0.0:4152" 15 | 16 | ## address that will be registered with lookupd (defaults to the OS hostname) 17 | # broadcast_address = "" 18 | 19 | ## cluster of nsqlookupd TCP addresses 20 | nsqlookupd_tcp_addresses = [ 21 | "127.0.0.1:4160" 22 | ] 23 | 24 | ## duration to wait before HTTP client connection timeout 25 | http_client_connect_timeout = "2s" 26 | 27 | ## duration to wait before HTTP client request timeout 28 | http_client_request_timeout = "5s" 29 | 30 | ## path to store disk-backed messages 31 | # data_path = "/var/lib/nsq" 32 | 33 | ## number of messages to keep in memory (per topic/channel) 34 | mem_queue_size = 10000 35 | 36 | ## number of bytes per diskqueue file before rolling 37 | max_bytes_per_file = 104857600 38 | 39 | ## number of messages per diskqueue fsync 40 | sync_every = 2500 41 | 42 | ## duration of time per diskqueue fsync (time.Duration) 43 | sync_timeout = "2s" 44 | 45 | 46 | ## duration to wait before auto-requeing a message 47 | msg_timeout = "60s" 48 | 49 | ## maximum duration before a message will timeout 50 | max_msg_timeout = "15m" 51 | 52 | ## maximum size of a single message in bytes 53 | max_msg_size = 1024768 54 | 55 | ## maximum requeuing timeout for a message 56 | max_req_timeout = "1h" 57 | 58 | ## maximum size of a single command body 59 | max_body_size = 5123840 60 | 61 | 62 | ## maximum client configurable duration of time between client heartbeats 63 | max_heartbeat_interval = "60s" 64 | 65 | ## maximum RDY count for a client 66 | max_rdy_count = 2500 67 | 68 | ## maximum client configurable size (in bytes) for a client output buffer 69 | max_output_buffer_size = 65536 70 | 71 | ## maximum client configurable duration of time between flushing to a client (time.Duration) 72 | max_output_buffer_timeout = "1s" 73 | 74 | 75 | ## UDP : of a statsd daemon for pushing stats 76 | # statsd_address = "127.0.0.1:8125" 77 | 78 | ## prefix used for keys sent to statsd (%s for host replacement) 79 | statsd_prefix = "nsq.%s" 80 | 81 | ## duration between pushing to statsd (time.Duration) 82 | statsd_interval = "60s" 83 | 84 | ## toggle sending memory and GC stats to statsd 85 | statsd_mem_stats = true 86 | 87 | 88 | ## message processing time percentiles to keep track of (float) 89 | e2e_processing_latency_percentiles = [ 90 | 100.0, 91 | 99.0, 92 | 95.0 93 | ] 94 | 95 | ## calculate end to end latency quantiles for this duration of time (time.Duration) 96 | e2e_processing_latency_window_time = "10m" 97 | 98 | 99 | ## path to certificate file 100 | tls_cert = "" 101 | 102 | ## path to private key file 103 | tls_key = "" 104 | 105 | ## set policy on client certificate (require - client must provide certificate, 106 | ## require-verify - client must provide verifiable signed certificate) 107 | # tls_client_auth_policy = "require-verify" 108 | 109 | ## set custom root Certificate Authority 110 | # tls_root_ca_file = "" 111 | 112 | ## require client TLS upgrades 113 | tls_required = false 114 | 115 | ## minimum TLS version ("ssl3.0", "tls1.0," "tls1.1", "tls1.2") 116 | tls_min_version = "" 117 | 118 | ## enable deflate feature negotiation (client compression) 119 | deflate = true 120 | 121 | ## max deflate compression level a client can negotiate (> values == > nsqd CPU usage) 122 | max_deflate_level = 6 123 | 124 | ## enable snappy feature negotiation (client compression) 125 | snappy = true 126 | -------------------------------------------------------------------------------- /apps/to_nsq/to_nsq.go: -------------------------------------------------------------------------------- 1 | // This is an NSQ client that publishes incoming messages from 2 | // stdin to the specified topic. 3 | 4 | package main 5 | 6 | import ( 7 | "bufio" 8 | "flag" 9 | "fmt" 10 | "io" 11 | "log" 12 | "os" 13 | "os/signal" 14 | "sync/atomic" 15 | "syscall" 16 | "time" 17 | 18 | "github.com/nsqio/go-nsq" 19 | "github.com/nsqio/nsq/internal/app" 20 | "github.com/nsqio/nsq/internal/version" 21 | ) 22 | 23 | var ( 24 | topic = flag.String("topic", "", "NSQ topic to publish to") 25 | delimiter = flag.String("delimiter", "\n", "character to split input from stdin") 26 | 27 | destNsqdTCPAddrs = app.StringArray{} 28 | ) 29 | 30 | func init() { 31 | flag.Var(&destNsqdTCPAddrs, "nsqd-tcp-address", "destination nsqd TCP address (may be given multiple times)") 32 | } 33 | 34 | func main() { 35 | cfg := nsq.NewConfig() 36 | flag.Var(&nsq.ConfigFlag{cfg}, "producer-opt", "option to passthrough to nsq.Producer (may be given multiple times, http://godoc.org/github.com/nsqio/go-nsq#Config)") 37 | rate := flag.Int64("rate", 0, "Throttle messages to n/second. 0 to disable") 38 | 39 | flag.Parse() 40 | 41 | if len(*topic) == 0 { 42 | log.Fatal("--topic required") 43 | } 44 | 45 | if len(*delimiter) != 1 { 46 | log.Fatal("--delimiter must be a single byte") 47 | } 48 | 49 | stopChan := make(chan bool) 50 | termChan := make(chan os.Signal, 1) 51 | signal.Notify(termChan, syscall.SIGINT, syscall.SIGTERM) 52 | 53 | cfg.UserAgent = fmt.Sprintf("to_nsq/%s go-nsq/%s", version.Binary, nsq.VERSION) 54 | 55 | // make the producers 56 | producers := make(map[string]*nsq.Producer) 57 | for _, addr := range destNsqdTCPAddrs { 58 | producer, err := nsq.NewProducer(addr, cfg) 59 | if err != nil { 60 | log.Fatalf("failed to create nsq.Producer - %s", err) 61 | } 62 | producers[addr] = producer 63 | } 64 | 65 | if len(producers) == 0 { 66 | log.Fatal("--nsqd-tcp-address required") 67 | } 68 | 69 | throttleEnabled := *rate >= 1 70 | balance := int64(1) 71 | interval := time.Second / time.Duration(*rate) 72 | go func() { 73 | if !throttleEnabled { 74 | return 75 | } 76 | log.Printf("Throttling messages rate to max:%d/second", *rate) 77 | // every tick increase the number of messages we can send 78 | for _ = range time.Tick(interval) { 79 | n := atomic.AddInt64(&balance, 1) 80 | // if we build up more than 1s of capacity just bound to that 81 | if n > int64(*rate) { 82 | atomic.StoreInt64(&balance, int64(*rate)) 83 | } 84 | } 85 | }() 86 | 87 | r := bufio.NewReader(os.Stdin) 88 | delim := (*delimiter)[0] 89 | go func() { 90 | for { 91 | var err error 92 | if throttleEnabled { 93 | currentBalance := atomic.LoadInt64(&balance) 94 | if currentBalance <= 0 { 95 | time.Sleep(interval) 96 | } 97 | err = readAndPublish(r, delim, producers) 98 | atomic.AddInt64(&balance, -1) 99 | } else { 100 | err = readAndPublish(r, delim, producers) 101 | } 102 | if err != nil { 103 | if err != io.EOF { 104 | log.Fatal(err) 105 | } 106 | close(stopChan) 107 | break 108 | } 109 | } 110 | }() 111 | 112 | select { 113 | case <-termChan: 114 | case <-stopChan: 115 | } 116 | 117 | for _, producer := range producers { 118 | producer.Stop() 119 | } 120 | } 121 | 122 | // readAndPublish reads to the delim from r and publishes the bytes 123 | // to the map of producers. 124 | func readAndPublish(r *bufio.Reader, delim byte, producers map[string]*nsq.Producer) error { 125 | line, readErr := r.ReadBytes(delim) 126 | 127 | if len(line) > 0 { 128 | // trim the delimiter 129 | line = line[:len(line)-1] 130 | } 131 | 132 | if len(line) == 0 { 133 | return readErr 134 | } 135 | 136 | for _, producer := range producers { 137 | err := producer.Publish(*topic, line) 138 | if err != nil { 139 | return err 140 | } 141 | } 142 | 143 | return readErr 144 | } 145 | -------------------------------------------------------------------------------- /nsqadmin/static/css/base.scss: -------------------------------------------------------------------------------- 1 | .red { 2 | color: #c30; 3 | } 4 | 5 | .red:hover { 6 | color: #f30; 7 | } 8 | 9 | .bold { 10 | font-weight: bold; 11 | } 12 | 13 | .graph-row td { 14 | text-align: center; 15 | } 16 | 17 | .image-preview { 18 | display: none; 19 | position: absolute; 20 | z-index: 100; 21 | height: 240px; 22 | width: 480px; 23 | } 24 | 25 | .white { 26 | color:#fff; 27 | } 28 | 29 | .bubblingG { 30 | text-align: center; 31 | width:125px; 32 | height:78px; 33 | } 34 | 35 | .bubblingG span { 36 | display: inline-block; 37 | vertical-align: middle; 38 | width: 16px; 39 | height: 16px; 40 | margin: 39px auto; 41 | background: #006FC4; 42 | -moz-border-radius: 79px; 43 | -moz-animation: bubblingG 0.9s infinite alternate; 44 | -webkit-border-radius: 79px; 45 | -webkit-animation: bubblingG 0.9s infinite alternate; 46 | -ms-border-radius: 79px; 47 | -ms-animation: bubblingG 0.9s infinite alternate; 48 | -o-border-radius: 79px; 49 | -o-animation: bubblingG 0.9s infinite alternate; 50 | border-radius: 79px; 51 | animation: bubblingG 0.9s infinite alternate; 52 | } 53 | 54 | #bubblingG_1 { 55 | -moz-animation-delay: 0s; 56 | -webkit-animation-delay: 0s; 57 | -ms-animation-delay: 0s; 58 | -o-animation-delay: 0s; 59 | animation-delay: 0s; 60 | } 61 | 62 | #bubblingG_2 { 63 | -moz-animation-delay: 0.27s; 64 | -webkit-animation-delay: 0.27s; 65 | -ms-animation-delay: 0.27s; 66 | -o-animation-delay: 0.27s; 67 | animation-delay: 0.27s; 68 | } 69 | 70 | #bubblingG_3 { 71 | -moz-animation-delay: 0.54s; 72 | -webkit-animation-delay: 0.54s; 73 | -ms-animation-delay: 0.54s; 74 | -o-animation-delay: 0.54s; 75 | animation-delay: 0.54s; 76 | } 77 | 78 | @-moz-keyframes bubblingG { 79 | 0% { 80 | width: 16px; 81 | height: 16px; 82 | background-color:#006FC4; 83 | -moz-transform: translateY(0); 84 | } 85 | 100% { 86 | width: 38px; 87 | height: 38px; 88 | background-color:#FFFFFF; 89 | -moz-transform: translateY(-33px); 90 | } 91 | } 92 | 93 | @-webkit-keyframes bubblingG { 94 | 0% { 95 | width: 16px; 96 | height: 16px; 97 | background-color:#006FC4; 98 | -webkit-transform: translateY(0); 99 | } 100 | 100% { 101 | width: 38px; 102 | height: 38px; 103 | background-color:#FFFFFF; 104 | -webkit-transform: translateY(-33px); 105 | } 106 | } 107 | 108 | @-ms-keyframes bubblingG { 109 | 0% { 110 | width: 16px; 111 | height: 16px; 112 | background-color:#006FC4; 113 | -ms-transform: translateY(0); 114 | } 115 | 100% { 116 | width: 38px; 117 | height: 38px; 118 | background-color:#FFFFFF; 119 | -ms-transform: translateY(-33px); 120 | } 121 | } 122 | 123 | @-o-keyframes bubblingG { 124 | 0% { 125 | width: 16px; 126 | height: 16px; 127 | background-color:#006FC4; 128 | -o-transform: translateY(0); 129 | } 130 | 100% { 131 | width: 38px; 132 | height: 38px; 133 | background-color:#FFFFFF; 134 | -o-transform: translateY(-33px); 135 | } 136 | } 137 | 138 | @keyframes bubblingG { 139 | 0% { 140 | width: 16px; 141 | height: 16px; 142 | background-color:#006FC4; 143 | transform: translateY(0); 144 | } 145 | 100% { 146 | width: 38px; 147 | height: 38px; 148 | background-color:#FFFFFF; 149 | transform: translateY(-33px); 150 | } 151 | } 152 | 153 | .bubblingG { 154 | position:absolute; 155 | left:0; right:0; 156 | top:0; bottom:0; 157 | margin:auto; 158 | max-width:100%; 159 | max-height:100%; 160 | overflow:auto; 161 | } 162 | 163 | .navbar-brand > img { 164 | width: 30px; 165 | height: 30px; 166 | margin-right: 5px; 167 | margin-top: -5px; 168 | display: inline; 169 | } -------------------------------------------------------------------------------- /nsqadmin/static/js/views/base.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | var _ = require('underscore'); 3 | var Backbone = require('backbone'); 4 | 5 | var AppState = require('../app_state'); 6 | 7 | var errorTemplate = require('./error.hbs'); 8 | 9 | var BaseView = Backbone.View.extend({ 10 | constructor: function(options) { 11 | // As of 1.10, Backbone no longer automatically attaches options passed 12 | // to the constructor as this.options, but that's often useful in some 13 | // cases, like a className function, that happen before initialize() 14 | // would have a chance to attach the same options. 15 | this.options = options || {}; 16 | return Backbone.View.prototype.constructor.apply(this, arguments); 17 | }, 18 | 19 | initialize: function() { 20 | this.subviews = []; 21 | this.rendered = false; 22 | }, 23 | 24 | template: function() {}, 25 | 26 | skippedRender: function() {}, 27 | 28 | render: function(data) { 29 | if (this.renderOnce && this.rendered) { 30 | this.skippedRender(); 31 | return this; 32 | } 33 | this.removeSubviews(); 34 | var ctx = this.getRenderCtx(data); 35 | // console.log('render ctx: %o', ctx); 36 | var html = this.template(ctx); 37 | if (!this.removed) { 38 | this.$el.empty(); 39 | this.$el.append(html); 40 | this.postRender(ctx); 41 | } 42 | this.rendered = true; 43 | return this; 44 | }, 45 | 46 | getRenderCtx: function(data) { 47 | var ctx = { 48 | 'graph_enabled': AppState.get('GRAPH_ENABLED'), 49 | 'graph_interval': AppState.get('graph_interval'), 50 | 'graph_active': AppState.get('GRAPH_ENABLED') && 51 | AppState.get('graph_interval') !== 'off', 52 | 'nsqlookupd': AppState.get('NSQLOOKUPD'), 53 | 'version': AppState.get('VERSION') 54 | }; 55 | if (this.model) { 56 | ctx = _.extend(ctx, this.model.toJSON()); 57 | } else if (this.collection) { 58 | ctx = _.extend(ctx, {'collection': this.collection.toJSON()}); 59 | } 60 | if (data) { 61 | ctx = _.extend(ctx, data); 62 | } 63 | return ctx; 64 | }, 65 | 66 | postRender: function() {}, 67 | 68 | appendSubview: function(subview, selector) { 69 | return this.appendSubviews([subview], selector); 70 | }, 71 | 72 | appendSubviews: function(subviews, selector) { 73 | this.subviews.push.apply(this.subviews, subviews); 74 | var $el = selector ? this.$(selector) : this.$el; 75 | $el.append(subviews.map(function(subview) { 76 | return subview.render().delegateEvents().el; 77 | })); 78 | }, 79 | 80 | removeSubviews: function() { 81 | while (this.subviews.length) { 82 | this.subviews.pop().remove(); 83 | } 84 | }, 85 | 86 | remove: function() { 87 | this.removed = true; 88 | this.removeSubviews(); 89 | Backbone.View.prototype.remove.apply(this, arguments); 90 | }, 91 | 92 | parseErrorMessage: function(jqXHR) { 93 | var msg = 'ERROR: failed to connect to nsqadmin'; 94 | if (jqXHR.readyState === 4) { 95 | try { 96 | var parsed = JSON.parse(jqXHR.responseText); 97 | msg = parsed['message']; 98 | } catch(err) { 99 | msg = 'ERROR: failed to decode JSON - ' + err.message; 100 | } 101 | } 102 | return msg; 103 | }, 104 | 105 | handleAJAXError: function(jqXHR) { 106 | $('#warning, #error').hide(); 107 | $('#error .alert').text(this.parseErrorMessage(jqXHR)); 108 | $('#error').show(); 109 | }, 110 | 111 | handleViewError: function(jqXHR) { 112 | this.removeSubviews(); 113 | this.$el.html(errorTemplate({'message': this.parseErrorMessage(jqXHR)})); 114 | } 115 | }); 116 | 117 | module.exports = BaseView; 118 | -------------------------------------------------------------------------------- /apps/nsqadmin/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | "time" 11 | 12 | "github.com/BurntSushi/toml" 13 | "github.com/mreiferson/go-options" 14 | "github.com/nsqio/nsq/internal/app" 15 | "github.com/nsqio/nsq/internal/version" 16 | "github.com/nsqio/nsq/nsqadmin" 17 | ) 18 | 19 | var ( 20 | flagSet = flag.NewFlagSet("nsqadmin", flag.ExitOnError) 21 | 22 | config = flagSet.String("config", "", "path to config file") 23 | showVersion = flagSet.Bool("version", false, "print version string") 24 | 25 | httpAddress = flagSet.String("http-address", "0.0.0.0:4171", ": to listen on for HTTP clients") 26 | templateDir = flagSet.String("template-dir", "", "path to templates directory") 27 | 28 | graphiteURL = flagSet.String("graphite-url", "", "graphite HTTP address") 29 | proxyGraphite = flagSet.Bool("proxy-graphite", false, "proxy HTTP requests to graphite") 30 | 31 | useStatsdPrefixes = flagSet.Bool("use-statsd-prefixes", true, "(Deprecated - Use --statsd-counter-format and --statsd-gauge-format) Expect statsd prefixed keys in graphite (ie: 'stats.counters.' and 'stats.gauges.')") 32 | statsdCounterFormat = flagSet.String("statsd-counter-format", "stats.counters.%s.count", "The counter stats key formatting applied by the implementation of statsd. If no formatting is desired, set this to an empty string.") 33 | statsdGaugeFormat = flagSet.String("statsd-gauge-format", "stats.gauges.%s", "The gauge stats key formatting applied by the implementation of statsd. If no formatting is desired, set this to an empty string.") 34 | statsdPrefix = flagSet.String("statsd-prefix", "nsq.%s", "prefix used for keys sent to statsd (%s for host replacement, must match nsqd)") 35 | statsdInterval = flagSet.Duration("statsd-interval", 60*time.Second, "time interval nsqd is configured to push to statsd (must match nsqd)") 36 | 37 | notificationHTTPEndpoint = flagSet.String("notification-http-endpoint", "", "HTTP endpoint (fully qualified) to which POST notifications of admin actions will be sent") 38 | 39 | httpConnectTimeout = flagSet.Duration("http-client-connect-timeout", 2*time.Second, "timeout for HTTP connect") 40 | httpRequestTimeout = flagSet.Duration("http-client-request-timeout", 5*time.Second, "timeout for HTTP request") 41 | 42 | httpClientTLSInsecureSkipVerify = flagSet.Bool("http-client-tls-insecure-skip-verify", false, "configure the HTTP client to skip verification of TLS certificates") 43 | httpClientTLSRootCAFile = flagSet.String("http-client-tls-root-ca-file", "", "path to CA file for the HTTP client") 44 | httpClientTLSCert = flagSet.String("http-client-tls-cert", "", "path to certificate file for the HTTP client") 45 | httpClientTLSKey = flagSet.String("http-client-tls-key", "", "path to key file for the HTTP client") 46 | 47 | nsqlookupdHTTPAddresses = app.StringArray{} 48 | nsqdHTTPAddresses = app.StringArray{} 49 | ) 50 | 51 | func init() { 52 | flagSet.Var(&nsqlookupdHTTPAddresses, "lookupd-http-address", "lookupd HTTP address (may be given multiple times)") 53 | flagSet.Var(&nsqdHTTPAddresses, "nsqd-http-address", "nsqd HTTP address (may be given multiple times)") 54 | } 55 | 56 | func main() { 57 | flagSet.Parse(os.Args[1:]) 58 | 59 | if *showVersion { 60 | fmt.Println(version.String("nsqadmin")) 61 | return 62 | } 63 | 64 | if *templateDir != "" { 65 | log.Printf("WARNING: --template-dir is deprecated and will be removed in the next release (templates are now compiled into the binary)") 66 | } 67 | 68 | exitChan := make(chan int) 69 | signalChan := make(chan os.Signal, 1) 70 | go func() { 71 | <-signalChan 72 | exitChan <- 1 73 | }() 74 | signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) 75 | 76 | var cfg map[string]interface{} 77 | if *config != "" { 78 | _, err := toml.DecodeFile(*config, &cfg) 79 | if err != nil { 80 | log.Fatalf("ERROR: failed to load config file %s - %s", *config, err) 81 | } 82 | } 83 | 84 | opts := nsqadmin.NewOptions() 85 | options.Resolve(opts, flagSet, cfg) 86 | nsqadmin := nsqadmin.New(opts) 87 | 88 | nsqadmin.Main() 89 | <-exitChan 90 | nsqadmin.Exit() 91 | } 92 | -------------------------------------------------------------------------------- /nsqd/lookup_peer.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | "net" 8 | "time" 9 | 10 | "github.com/nsqio/go-nsq" 11 | ) 12 | 13 | // lookupPeer is a low-level type for connecting/reading/writing to nsqlookupd 14 | // 15 | // A lookupPeer instance is designed to connect lazily to nsqlookupd and reconnect 16 | // gracefully (i.e. it is all handled by the library). Clients can simply use the 17 | // Command interface to perform a round-trip. 18 | type lookupPeer struct { 19 | l Logger 20 | addr string 21 | conn net.Conn 22 | state int32 23 | connectCallback func(*lookupPeer) 24 | maxBodySize int64 25 | Info peerInfo 26 | } 27 | 28 | // peerInfo contains metadata for a lookupPeer instance (and is JSON marshalable) 29 | type peerInfo struct { 30 | TCPPort int `json:"tcp_port"` 31 | HTTPPort int `json:"http_port"` 32 | Version string `json:"version"` 33 | BroadcastAddress string `json:"broadcast_address"` 34 | } 35 | 36 | // newLookupPeer creates a new lookupPeer instance connecting to the supplied address. 37 | // 38 | // The supplied connectCallback will be called *every* time the instance connects. 39 | func newLookupPeer(addr string, maxBodySize int64, l Logger, connectCallback func(*lookupPeer)) *lookupPeer { 40 | return &lookupPeer{ 41 | l: l, 42 | addr: addr, 43 | state: stateDisconnected, 44 | maxBodySize: maxBodySize, 45 | connectCallback: connectCallback, 46 | } 47 | } 48 | 49 | // Connect will Dial the specified address, with timeouts 50 | func (lp *lookupPeer) Connect() error { 51 | lp.l.Output(2, fmt.Sprintf("LOOKUP connecting to %s", lp.addr)) 52 | conn, err := net.DialTimeout("tcp", lp.addr, time.Second) 53 | if err != nil { 54 | return err 55 | } 56 | lp.conn = conn 57 | return nil 58 | } 59 | 60 | // String returns the specified address 61 | func (lp *lookupPeer) String() string { 62 | return lp.addr 63 | } 64 | 65 | // Read implements the io.Reader interface, adding deadlines 66 | func (lp *lookupPeer) Read(data []byte) (int, error) { 67 | lp.conn.SetReadDeadline(time.Now().Add(time.Second)) 68 | return lp.conn.Read(data) 69 | } 70 | 71 | // Write implements the io.Writer interface, adding deadlines 72 | func (lp *lookupPeer) Write(data []byte) (int, error) { 73 | lp.conn.SetWriteDeadline(time.Now().Add(time.Second)) 74 | return lp.conn.Write(data) 75 | } 76 | 77 | // Close implements the io.Closer interface 78 | func (lp *lookupPeer) Close() error { 79 | lp.state = stateDisconnected 80 | if lp.conn != nil { 81 | return lp.conn.Close() 82 | } 83 | return nil 84 | } 85 | 86 | // Command performs a round-trip for the specified Command. 87 | // 88 | // It will lazily connect to nsqlookupd and gracefully handle 89 | // reconnecting in the event of a failure. 90 | // 91 | // It returns the response from nsqlookupd as []byte 92 | func (lp *lookupPeer) Command(cmd *nsq.Command) ([]byte, error) { 93 | initialState := lp.state 94 | if lp.state != stateConnected { 95 | err := lp.Connect() 96 | if err != nil { 97 | return nil, err 98 | } 99 | lp.state = stateConnected 100 | lp.Write(nsq.MagicV1) 101 | if initialState == stateDisconnected { 102 | lp.connectCallback(lp) 103 | } 104 | } 105 | if cmd == nil { 106 | return nil, nil 107 | } 108 | _, err := cmd.WriteTo(lp) 109 | if err != nil { 110 | lp.Close() 111 | return nil, err 112 | } 113 | resp, err := readResponseBounded(lp, lp.maxBodySize) 114 | if err != nil { 115 | lp.Close() 116 | return nil, err 117 | } 118 | return resp, nil 119 | } 120 | 121 | func readResponseBounded(r io.Reader, limit int64) ([]byte, error) { 122 | var msgSize int32 123 | 124 | // message size 125 | err := binary.Read(r, binary.BigEndian, &msgSize) 126 | if err != nil { 127 | return nil, err 128 | } 129 | 130 | if int64(msgSize) > limit { 131 | return nil, fmt.Errorf("response body size (%d) is greater than limit (%d)", 132 | msgSize, limit) 133 | } 134 | 135 | // message binary data 136 | buf := make([]byte, msgSize) 137 | _, err = io.ReadFull(r, buf) 138 | if err != nil { 139 | return nil, err 140 | } 141 | 142 | return buf, nil 143 | } 144 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/counter.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | var $ = require('jquery'); 3 | 4 | var AppState = require('../app_state'); 5 | 6 | var BaseView = require('./base'); 7 | 8 | var CounterView = BaseView.extend({ 9 | className: 'counter container-fluid', 10 | 11 | template: require('./counter.hbs'), 12 | 13 | initialize: function() { 14 | BaseView.prototype.initialize.apply(this, arguments); 15 | this.listenTo(AppState, 'change:graph_interval', this.render); 16 | this.start(); 17 | }, 18 | 19 | remove: function() { 20 | clearTimeout(this.poller); 21 | clearTimeout(this.animator); 22 | BaseView.prototype.remove.apply(this, arguments); 23 | }, 24 | 25 | start: function() { 26 | this.poller = null; 27 | this.animator = null; 28 | this.delta = 0; 29 | this.looping = false; 30 | this.targetPollInterval = 10000; 31 | this.currentNum = -1; 32 | this.interval = 100; 33 | this.updateStats(); 34 | }, 35 | 36 | startLoop: function(i) { 37 | this.interval = i; 38 | this.poller = setTimeout(this.updateStats.bind(this), i); 39 | }, 40 | 41 | updateStats: function() { 42 | $.get(AppState.url('/counter')).done(function(data) { 43 | if (this.removed) { 44 | return; 45 | } 46 | 47 | var num = _.reduce(data['stats'], function(n, v) { 48 | return n + v['message_count']; 49 | }, 0); 50 | 51 | if (this.currentNum === -1) { 52 | // seed the display 53 | this.currentNum = num; 54 | this.writeCounts(this.currentNum); 55 | } else if (num > this.lastNum) { 56 | var delta = num - this.lastNum; 57 | this.delta = (delta / (this.interval / 1000)) / 50; 58 | if (!this.animator) { 59 | this.displayFrame(); 60 | } 61 | } 62 | this.currentNum = this.lastNum; 63 | this.lastNum = num; 64 | 65 | var newInterval = this.interval; 66 | if (newInterval < this.targetPollInterval) { 67 | newInterval = this.interval + 1000; 68 | } 69 | this.startLoop(newInterval); 70 | 71 | $('#warning, #error').hide(); 72 | if (data['message'] !== '') { 73 | $('#warning .alert').text(data['message']); 74 | $('#warning').show(); 75 | } 76 | }.bind(this)).fail(function(jqXHR) { 77 | if (this.removed) { 78 | return; 79 | } 80 | 81 | clearTimeout(this.animator); 82 | this.animator = null; 83 | 84 | this.startLoop(10000); 85 | 86 | this.handleAJAXError(jqXHR); 87 | }.bind(this)); 88 | 89 | if ($('#big_graph').length) { 90 | $('#big_graph').attr('src', LARGE_GRAPH_URL + '&_uniq=' + Math.random() * 1000000); 91 | } 92 | }, 93 | 94 | displayFrame: function() { 95 | this.currentNum += this.delta; 96 | this.writeCounts(this.currentNum); 97 | this.animator = setTimeout(this.displayFrame.bind(this), 1000 / 60); 98 | }, 99 | 100 | writeCounts: function(c) { 101 | var text = parseInt(c, 10).toString(); 102 | var node = $('.numbers')[0]; 103 | var n = $('.numbers .number'); 104 | for (var i = 0; i < text.length; i++) { 105 | var v = text.charAt(i); 106 | if (n.length > i) { 107 | var el = $(n[i]); 108 | el.show(); 109 | el.find('.top').text(v); 110 | el.find('.bottom').text(v); 111 | } else { 112 | $(node).append('' + v + 113 | '' + v + '\n'); 114 | } 115 | } 116 | $('.numbers .number').each(function(ii, vv) { 117 | if (ii >= text.length) { 118 | $(vv).hide(); 119 | } 120 | }); 121 | } 122 | }); 123 | 124 | module.exports = CounterView; 125 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/counter.hbs: -------------------------------------------------------------------------------- 1 | 196 | 197 | {{> warning}} 198 | {{> error}} 199 | 200 |
201 |
202 |
203 |

Messages Processed

204 |

205 | {{#if graph_active}} 206 | 209 | 210 | {{/if}} 211 |
212 | -------------------------------------------------------------------------------- /nsqadmin/gulpfile.js: -------------------------------------------------------------------------------- 1 | var browserify = require('browserify'); 2 | var clean = require('gulp-clean'); 3 | var gulp = require('gulp'); 4 | var notify = require('gulp-notify'); 5 | var path = require('path'); 6 | var sass = require('gulp-sass'); 7 | var source = require('vinyl-source-stream'); 8 | var taskListing = require('gulp-task-listing'); 9 | var uglify = require('gulp-uglify'); 10 | var sourcemaps = require('gulp-sourcemaps'); 11 | var buffer = require('vinyl-buffer'); 12 | 13 | 14 | var ROOT = 'static'; 15 | 16 | var VENDOR_CONFIG = { 17 | 'src': [ 18 | 'backbone', 19 | 'jquery', 20 | 'underscore', 21 | 'bootbox', 22 | ], 23 | 'target': 'vendor.js', 24 | 'targetDir': './static/build/' 25 | }; 26 | 27 | function excludeVendor(b) { 28 | VENDOR_CONFIG.src.forEach(function(vendorLib) { 29 | b.exclude(vendorLib); 30 | }); 31 | } 32 | 33 | function bytesToKB(bytes) { return Math.floor(+bytes/1024); } 34 | 35 | function logBundle(filename, watching) { 36 | return function (err, buf) { 37 | if (err) { 38 | console.error(err.toString()); 39 | if (!watching) { 40 | process.exit(1); 41 | } 42 | } 43 | if (!watching) { 44 | console.log(filename + ' ' + bytesToKB(buf.length) + ' KB written'); 45 | } 46 | } 47 | } 48 | 49 | 50 | function sassTask(root, inputFile) { 51 | return function() { 52 | var onError = function(err) { 53 | notify({'title': 'Sass Compile Error'}).write(err); 54 | }; 55 | gulp.src(path.join(root, 'css', inputFile)) 56 | .pipe(sass({ 57 | 'sourceComments': 'map', 58 | 'onError': onError 59 | })) 60 | .pipe(gulp.dest(path.join(root, 'build/'))); 61 | }; 62 | } 63 | 64 | 65 | function browserifyTask(root, inputFile) { 66 | return function() { 67 | var onError = function() { 68 | var args = Array.prototype.slice.call(arguments); 69 | notify.onError({ 70 | 'title': 'JS Compile Error', 71 | 'message': '<%= error.message %>' 72 | }).apply(this, args); 73 | // Keep gulp from hanging on this task 74 | this.emit('end'); 75 | }; 76 | 77 | // Browserify needs a node module to import as its arg, so we need to 78 | // force the leading "./" to be included. 79 | var b = browserify({ 80 | entries: './' + path.join(root, 'js', inputFile), 81 | debug: true 82 | }) 83 | 84 | excludeVendor(b); 85 | 86 | return b.bundle() 87 | .pipe(source(inputFile)) 88 | .pipe(buffer()) 89 | .pipe(sourcemaps.init({'loadMaps': true, 'debug': true})) 90 | // Add transformation tasks to the pipeline here. 91 | .pipe(uglify()) 92 | .on('error', onError) 93 | .pipe(sourcemaps.write('./')) 94 | .pipe(gulp.dest(path.join(root, 'build/'))); 95 | }; 96 | } 97 | 98 | 99 | function watchTask(root) { 100 | return function() { 101 | gulp.watch(path.join(root, 'sass/**/*.scss'), ['sass']); 102 | gulp.watch([ 103 | path.join(root, 'js/**/*.js'), 104 | path.join(root, 'js/**/*.hbs') 105 | ], ['browserify']); 106 | gulp.watch([ 107 | path.join(root, 'html/**'), 108 | path.join(root, 'fonts/**') 109 | ], ['sync-static-assets']) 110 | }; 111 | } 112 | 113 | 114 | function cleanTask(){ 115 | var paths = Array.prototype.slice.apply(arguments); 116 | return function () { 117 | gulp.src(paths).pipe(clean()); 118 | }; 119 | } 120 | 121 | gulp.task('vendor-build-js', function() { 122 | var onError = function() { 123 | var args = Array.prototype.slice.call(arguments); 124 | notify.onError({ 125 | 'title': 'JS Compile Error', 126 | 'message': '<%= error.message %>' 127 | }).apply(this, args); 128 | // Keep gulp from hanging on this task 129 | this.emit('end'); 130 | }; 131 | 132 | var b = browserify() 133 | .require(VENDOR_CONFIG.src); 134 | 135 | return b.bundle(logBundle(VENDOR_CONFIG.target)) 136 | .pipe(source(VENDOR_CONFIG.target)) 137 | .pipe(buffer()) 138 | // Add transformation tasks to the pipeline here. 139 | .pipe(uglify()) 140 | .on('error', onError) 141 | .pipe(gulp.dest(VENDOR_CONFIG.targetDir)); 142 | }); 143 | 144 | gulp.task('help', taskListing); 145 | 146 | gulp.task('sync-static-assets', function() { 147 | return gulp.src([ 148 | path.join(ROOT, 'html/**'), 149 | path.join(ROOT, 'fonts/**'), 150 | path.join(ROOT, 'img/**') 151 | ]).pipe(gulp.dest(path.join(ROOT, 'build'))); 152 | }); 153 | 154 | gulp.task('sass', sassTask(ROOT, '*.*css')); 155 | gulp.task('browserify', browserifyTask(ROOT, 'main.js')); 156 | gulp.task('build', ['sass', 'browserify', 'sync-static-assets', 'vendor-build-js']); 157 | gulp.task('watch', ['build'], watchTask(ROOT)); 158 | gulp.task('clean', cleanTask(path.join(ROOT, 'build'))); 159 | 160 | gulp.task('default', ['help']); 161 | -------------------------------------------------------------------------------- /nsqd/options.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | import ( 4 | "crypto/md5" 5 | "crypto/tls" 6 | "hash/crc32" 7 | "io" 8 | "log" 9 | "os" 10 | "time" 11 | ) 12 | 13 | type Options struct { 14 | // basic options 15 | ID int64 `flag:"worker-id" cfg:"id"` 16 | Verbose bool `flag:"verbose"` 17 | TCPAddress string `flag:"tcp-address"` 18 | HTTPAddress string `flag:"http-address"` 19 | HTTPSAddress string `flag:"https-address"` 20 | BroadcastAddress string `flag:"broadcast-address"` 21 | NSQLookupdTCPAddresses []string `flag:"lookupd-tcp-address" cfg:"nsqlookupd_tcp_addresses"` 22 | AuthHTTPAddresses []string `flag:"auth-http-address" cfg:"auth_http_addresses"` 23 | HTTPClientConnectTimeout time.Duration `flag:"http-client-connect-timeout" cfg:"http_client_connect_timeout"` 24 | HTTPClientRequestTimeout time.Duration `flag:"http-client-request-timeout" cfg:"http_client_request_timeout"` 25 | 26 | // diskqueue options 27 | DataPath string `flag:"data-path"` 28 | MemQueueSize int64 `flag:"mem-queue-size"` 29 | MaxBytesPerFile int64 `flag:"max-bytes-per-file"` 30 | SyncEvery int64 `flag:"sync-every"` 31 | SyncTimeout time.Duration `flag:"sync-timeout"` 32 | 33 | QueueScanInterval time.Duration 34 | QueueScanRefreshInterval time.Duration 35 | QueueScanSelectionCount int 36 | QueueScanWorkerPoolMax int 37 | QueueScanDirtyPercent float64 38 | 39 | // msg and command options 40 | MsgTimeout time.Duration `flag:"msg-timeout" arg:"1ms"` 41 | MaxMsgTimeout time.Duration `flag:"max-msg-timeout"` 42 | MaxMsgSize int64 `flag:"max-msg-size" deprecated:"max-message-size" cfg:"max_msg_size"` 43 | MaxBodySize int64 `flag:"max-body-size"` 44 | MaxReqTimeout time.Duration `flag:"max-req-timeout"` 45 | ClientTimeout time.Duration 46 | 47 | // client overridable configuration options 48 | MaxHeartbeatInterval time.Duration `flag:"max-heartbeat-interval"` 49 | MaxRdyCount int64 `flag:"max-rdy-count"` 50 | MaxOutputBufferSize int64 `flag:"max-output-buffer-size"` 51 | MaxOutputBufferTimeout time.Duration `flag:"max-output-buffer-timeout"` 52 | 53 | // statsd integration 54 | StatsdAddress string `flag:"statsd-address"` 55 | StatsdPrefix string `flag:"statsd-prefix"` 56 | StatsdInterval time.Duration `flag:"statsd-interval" arg:"1s"` 57 | StatsdMemStats bool `flag:"statsd-mem-stats"` 58 | 59 | // e2e message latency 60 | E2EProcessingLatencyWindowTime time.Duration `flag:"e2e-processing-latency-window-time"` 61 | E2EProcessingLatencyPercentiles []float64 `flag:"e2e-processing-latency-percentile" cfg:"e2e_processing_latency_percentiles"` 62 | 63 | // TLS config 64 | TLSCert string `flag:"tls-cert"` 65 | TLSKey string `flag:"tls-key"` 66 | TLSClientAuthPolicy string `flag:"tls-client-auth-policy"` 67 | TLSRootCAFile string `flag:"tls-root-ca-file"` 68 | TLSRequired int `flag:"tls-required"` 69 | TLSMinVersion uint16 `flag:"tls-min-version"` 70 | 71 | // compression 72 | DeflateEnabled bool `flag:"deflate"` 73 | MaxDeflateLevel int `flag:"max-deflate-level"` 74 | SnappyEnabled bool `flag:"snappy"` 75 | 76 | Logger Logger 77 | } 78 | 79 | func NewOptions() *Options { 80 | hostname, err := os.Hostname() 81 | if err != nil { 82 | log.Fatal(err) 83 | } 84 | 85 | h := md5.New() 86 | io.WriteString(h, hostname) 87 | defaultID := int64(crc32.ChecksumIEEE(h.Sum(nil)) % 1024) 88 | 89 | return &Options{ 90 | ID: defaultID, 91 | 92 | TCPAddress: "0.0.0.0:4150", 93 | HTTPAddress: "0.0.0.0:4151", 94 | HTTPSAddress: "0.0.0.0:4152", 95 | BroadcastAddress: hostname, 96 | 97 | NSQLookupdTCPAddresses: make([]string, 0), 98 | AuthHTTPAddresses: make([]string, 0), 99 | 100 | HTTPClientConnectTimeout: 2 * time.Second, 101 | HTTPClientRequestTimeout: 5 * time.Second, 102 | 103 | MemQueueSize: 10000, 104 | MaxBytesPerFile: 100 * 1024 * 1024, 105 | SyncEvery: 2500, 106 | SyncTimeout: 2 * time.Second, 107 | 108 | QueueScanInterval: 100 * time.Millisecond, 109 | QueueScanRefreshInterval: 5 * time.Second, 110 | QueueScanSelectionCount: 20, 111 | QueueScanWorkerPoolMax: 4, 112 | QueueScanDirtyPercent: 0.25, 113 | 114 | MsgTimeout: 60 * time.Second, 115 | MaxMsgTimeout: 15 * time.Minute, 116 | MaxMsgSize: 1024 * 1024, 117 | MaxBodySize: 5 * 1024 * 1024, 118 | MaxReqTimeout: 1 * time.Hour, 119 | ClientTimeout: 60 * time.Second, 120 | 121 | MaxHeartbeatInterval: 60 * time.Second, 122 | MaxRdyCount: 2500, 123 | MaxOutputBufferSize: 64 * 1024, 124 | MaxOutputBufferTimeout: 1 * time.Second, 125 | 126 | StatsdPrefix: "nsq.%s", 127 | StatsdInterval: 60 * time.Second, 128 | StatsdMemStats: true, 129 | 130 | E2EProcessingLatencyWindowTime: time.Duration(10 * time.Minute), 131 | 132 | DeflateEnabled: true, 133 | MaxDeflateLevel: 6, 134 | SnappyEnabled: true, 135 | 136 | TLSMinVersion: tls.VersionTLS10, 137 | 138 | Logger: log.New(os.Stderr, "[nsqd] ", log.Ldate|log.Ltime|log.Lmicroseconds), 139 | } 140 | } 141 | --------------------------------------------------------------------------------