├── 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 |
9 |
10 |
11 |
12 | {{#if collection.length}}
13 |
14 |
15 | Topic
16 | {{#if graph_active}}Depth {{/if}}
17 | {{#if graph_active}}Messages {{/if}}
18 | {{#if graph_active}}Rate {{/if}}
19 |
20 | {{#each collection}}
21 |
22 | {{name}}
23 | {{#if ../graph_active}} {{/if}}
24 | {{#if ../graph_active}} {{/if}}
25 | {{#if ../graph_active}} {{/if}}
26 |
27 | {{/each}}
28 |
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 |
2 |
38 |
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 | Hostname
15 | Broadcast Address
16 | TCP Port
17 | HTTP Port
18 | Version
19 | {{#if nsqlookupd.length}}
20 | Lookupd Conns.
21 | {{/if}}
22 | Topics
23 |
24 | {{#each collection}}
25 |
26 | {{hostname}}
27 | {{broadcast_address}}
28 | {{tcp_port}}
29 | {{http_port}}
30 | {{version}}
31 | {{#if ../nsqlookupd.length}}
32 |
33 | {{remote_addresses.length}}
34 |
35 | {{#each remote_addresses}}{{this}} {{/each}}
36 |
37 |
38 | {{/if}}
39 |
40 | {{#if topics.length}}
41 | {{topics.length}}
42 | {{#each topics}}
43 | {{topic}}
44 | {{/each}}
45 | {{/if}}
46 |
47 |
48 | {{/each}}
49 |
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 |
9 |
10 | {{#unless nsqlookupd.length}}
11 |
12 |
Notice nsqadmin is not configured with nsqlookupd hosts
13 |
14 | {{else}}
15 |
16 |
17 |
18 |
19 | nsqlookupd Host
20 |
21 | {{#each nsqlookupd}}
22 | {{this}}
23 | {{/each}}
24 |
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 |
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 |
--------------------------------------------------------------------------------