<commit_before>// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"flag"
	"fmt"
	_ "net/http/pprof" // Comment this line to disable pprof endpoint.
	"os"
	"os/signal"
	"sort"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/golang/glog"

	clientmodel "github.com/prometheus/client_golang/model"
	registry "github.com/prometheus/client_golang/prometheus"

	"github.com/prometheus/prometheus/config"
	"github.com/prometheus/prometheus/notification"
	"github.com/prometheus/prometheus/retrieval"
	"github.com/prometheus/prometheus/rules/manager"
	"github.com/prometheus/prometheus/storage"
	"github.com/prometheus/prometheus/storage/local"
	"github.com/prometheus/prometheus/storage/remote"
	"github.com/prometheus/prometheus/storage/remote/influxdb"
	"github.com/prometheus/prometheus/storage/remote/opentsdb"
	"github.com/prometheus/prometheus/web"
	"github.com/prometheus/prometheus/web/api"
)

const deletionBatchSize = 100

// Commandline flags.
var (
	configFile = flag.String("config.file", "prometheus.conf", "Prometheus configuration file name.")

	alertmanagerURL           = flag.String("alertmanager.url", "", "The URL of the alert manager to send notifications to.")
	notificationQueueCapacity = flag.Int("alertmanager.notification-queue-capacity", 100, "The capacity of the queue for pending alert manager notifications.")

	persistenceStoragePath = flag.String("storage.local.path", "/tmp/metrics", "Base path for metrics storage.")

	opentsdbURL          = flag.String("storage.remote.opentsdb-url", "", "The URL of the remote OpenTSDB server to send samples to. None, if empty.")
	influxdbURL          = flag.String("storage.remote.influxdb-url", "", "The URL of the remote InfluxDB server to send samples to. None, if empty.")
	remoteStorageTimeout = flag.Duration("storage.remote.timeout", 30*time.Second, "The timeout to use when sending samples to the remote storage.")

	numMemoryChunks = flag.Int("storage.local.memory-chunks", 1024*1024, "How many chunks to keep in memory. While the size of a chunk is 1kiB, the total memory usage will be significantly higher than this value * 1kiB. Furthermore, for various reasons, more chunks might have to be kept in memory temporarily.")

	persistenceRetentionPeriod = flag.Duration("storage.local.retention", 15*24*time.Hour, "How long to retain samples in the local storage.")
	maxChunksToPersist         = flag.Int("storage.local.max-chunks-to-persist", 1024*1024, "How many chunks can be waiting for persistence before sample ingestion will stop. Many chunks waiting to be persisted will increase the checkpoint size.")

	checkpointInterval         = flag.Duration("storage.local.checkpoint-interval", 5*time.Minute, "The period at which the in-memory metrics and the chunks not yet persisted to series files are checkpointed.")
	checkpointDirtySeriesLimit = flag.Int("storage.local.checkpoint-dirty-series-limit", 5000, "If approx. that many time series are in a state that would require a recovery operation after a crash, a checkpoint is triggered, even if the checkpoint interval hasn't passed yet. A recovery operation requires a disk seek. The default limit intends to keep the recovery time below 1min even on spinning disks. With SSD, recovery is much faster, so you might want to increase this value in that case to avoid overly frequent checkpoints.")
	seriesSyncStrategy         = flag.String("storage.local.series-sync-strategy", "adaptive", "When to sync series files after modification. Possible values: 'never', 'always', 'adaptive'. Sync'ing slows down storage performance but reduces the risk of data loss in case of an OS crash. With the 'adaptive' strategy, series files are sync'd for as long as the storage is not too much behind on chunk persistence.")

	storageDirty          = flag.Bool("storage.local.dirty", false, "If set, the local storage layer will perform crash recovery even if the last shutdown appears to be clean.")
	storagePedanticChecks = flag.Bool("storage.local.pedantic-checks", false, "If set, a crash recovery will perform checks on each series file. This might take a very long time.")

	pathPrefix = flag.String("web.path-prefix", "/", "Prefix for all web paths.")

	printVersion = flag.Bool("version", false, "Print version information.")
)

type prometheus struct {
	ruleManager         manager.RuleManager
	targetManager       retrieval.TargetManager
	notificationHandler *notification.NotificationHandler
	storage             local.Storage
	remoteStorageQueues []*remote.StorageQueueManager

	webService *web.WebService

	closeOnce sync.Once
}

// NewPrometheus creates a new prometheus object based on flag values.
// Call Serve() to start serving and Close() for clean shutdown.
func NewPrometheus() *prometheus {
	conf, err := config.LoadFromFile(*configFile)
	if err != nil {
		glog.Errorf("Error loading configuration from %s: %v\n", *configFile, err)
		os.Exit(2)
	}

	notificationHandler := notification.NewNotificationHandler(*alertmanagerURL, *notificationQueueCapacity)

	var syncStrategy local.SyncStrategy
	switch *seriesSyncStrategy {
	case "never":
		syncStrategy = local.Never
	case "always":
		syncStrategy = local.Always
	case "adaptive":
		syncStrategy = local.Adaptive
	default:
		glog.Errorf("Invalid flag value for 'storage.local.series-sync-strategy': %s\n", *seriesSyncStrategy)
		os.Exit(2)
	}

	o := &local.MemorySeriesStorageOptions{
		MemoryChunks:               *numMemoryChunks,
		MaxChunksToPersist:         *maxChunksToPersist,
		PersistenceStoragePath:     *persistenceStoragePath,
		PersistenceRetentionPeriod: *persistenceRetentionPeriod,
		CheckpointInterval:         *checkpointInterval,
		CheckpointDirtySeriesLimit: *checkpointDirtySeriesLimit,
		Dirty:          *storageDirty,
		PedanticChecks: *storagePedanticChecks,
		SyncStrategy:   syncStrategy,
	}
	memStorage, err := local.NewMemorySeriesStorage(o)
	if err != nil {
		glog.Error("Error opening memory series storage: ", err)
		os.Exit(1)
	}

	var sampleAppender storage.SampleAppender
	var remoteStorageQueues []*remote.StorageQueueManager
	if *opentsdbURL == "" && *influxdbURL == "" {
		glog.Warningf("No remote storage URLs provided; not sending any samples to long-term storage")
		sampleAppender = memStorage
	} else {
		fanout := storage.Fanout{memStorage}

		addRemoteStorage := func(c remote.StorageClient) {
			qm := remote.NewStorageQueueManager(c, 100*1024)
			fanout = append(fanout, qm)
			remoteStorageQueues = append(remoteStorageQueues, qm)
		}

		if *opentsdbURL != "" {
			addRemoteStorage(opentsdb.NewClient(*opentsdbURL, *remoteStorageTimeout))
		}
		if *influxdbURL != "" {
			addRemoteStorage(influxdb.NewClient(*influxdbURL, *remoteStorageTimeout))
		}

		sampleAppender = fanout
	}

	targetManager := retrieval.NewTargetManager(sampleAppender, conf.GlobalLabels())
	targetManager.AddTargetsFromConfig(conf)

	ruleManager := manager.NewRuleManager(&manager.RuleManagerOptions{
		SampleAppender:      sampleAppender,
		NotificationHandler: notificationHandler,
		EvaluationInterval:  conf.EvaluationInterval(),
		Storage:             memStorage,
		PrometheusURL:       web.MustBuildServerURL(*pathPrefix),
		PathPrefix:          *pathPrefix,
	})
	if err := ruleManager.AddRulesFromConfig(conf); err != nil {
		glog.Error("Error loading rule files: ", err)
		os.Exit(1)
	}

	flags := map[string]string{}
	flag.VisitAll(func(f *flag.Flag) {
		flags[f.Name] = f.Value.String()
	})
	prometheusStatus := &web.PrometheusStatusHandler{
		BuildInfo:   BuildInfo,
		Config:      conf.String(),
		RuleManager: ruleManager,
		TargetPools: targetManager.Pools(),
		Flags:       flags,
		Birth:       time.Now(),
		PathPrefix:  *pathPrefix,
	}

	alertsHandler := &web.AlertsHandler{
		RuleManager: ruleManager,
		PathPrefix:  *pathPrefix,
	}

	consolesHandler := &web.ConsolesHandler{
		Storage:    memStorage,
		PathPrefix: *pathPrefix,
	}

	graphsHandler := &web.GraphsHandler{
		PathPrefix: *pathPrefix,
	}

	metricsService := &api.MetricsService{
		Now:     clientmodel.Now,
		Storage: memStorage,
	}

	webService := &web.WebService{
		StatusHandler:   prometheusStatus,
		MetricsHandler:  metricsService,
		ConsolesHandler: consolesHandler,
		AlertsHandler:   alertsHandler,
		GraphsHandler:   graphsHandler,
	}

	p := &prometheus{
		ruleManager:         ruleManager,
		targetManager:       targetManager,
		notificationHandler: notificationHandler,
		storage:             memStorage,
		remoteStorageQueues: remoteStorageQueues,

		webService: webService,
	}
	webService.QuitChan = make(chan struct{})
	return p
}

// Serve starts the Prometheus server. It returns after the server has been shut
// down. The method installs an interrupt handler, allowing to trigger a
// shutdown by sending SIGTERM to the process.
func (p *prometheus) Serve() {
	for _, q := range p.remoteStorageQueues {
		go q.Run()
	}
	go p.ruleManager.Run()
	go p.notificationHandler.Run()

	p.storage.Start()

	go func() {
		err := p.webService.ServeForever(*pathPrefix)
		if err != nil {
			glog.Fatal(err)
		}
	}()

	notifier := make(chan os.Signal)
	signal.Notify(notifier, os.Interrupt, syscall.SIGTERM)
	select {
	case <-notifier:
		glog.Warning("Received SIGTERM, exiting gracefully...")
	case <-p.webService.QuitChan:
		glog.Warning("Received termination request via web service, exiting gracefully...")
	}

	p.targetManager.Stop()
	p.ruleManager.Stop()

	if err := p.storage.Stop(); err != nil {
		glog.Error("Error stopping local storage: ", err)
	}

	for _, q := range p.remoteStorageQueues {
		q.Stop()
	}

	p.notificationHandler.Stop()
	glog.Info("See you next time!")
}

// Describe implements registry.Collector.
func (p *prometheus) Describe(ch chan<- *registry.Desc) {
	p.notificationHandler.Describe(ch)
	p.storage.Describe(ch)
	for _, q := range p.remoteStorageQueues {
		q.Describe(ch)
	}
}

// Collect implements registry.Collector.
func (p *prometheus) Collect(ch chan<- registry.Metric) {
	p.notificationHandler.Collect(ch)
	p.storage.Collect(ch)
	for _, q := range p.remoteStorageQueues {
		q.Collect(ch)
	}
}

func usage() {
	groups := make(map[string][]*flag.Flag)
	// Set a default group for ungrouped flags.
	groups["."] = make([]*flag.Flag, 0)

	// Bucket flags into groups based on the first of their dot-separated levels.
	flag.VisitAll(func(fl *flag.Flag) {
		parts := strings.SplitN(fl.Name, ".", 2)
		if len(parts) == 1 {
			groups["."] = append(groups["."], fl)
		} else {
			name := parts[0]
			groups[name] = append(groups[name], fl)
		}
	})

	groupsOrdered := make(sort.StringSlice, 0, len(groups))
	for groupName := range groups {
		groupsOrdered = append(groupsOrdered, groupName)
	}
	sort.Sort(groupsOrdered)

	fmt.Fprintf(os.Stderr, "Usage: %s [options ...] -config.file=<config_file>:\n\n", os.Args[0])

	const (
		maxLineLength = 80
		lineSep       = "\n      "
	)
	for _, groupName := range groupsOrdered {
		if groupName != "." {
			fmt.Fprintf(os.Stderr, "\n%s:\n", strings.Title(groupName))
		}

		for _, fl := range groups[groupName] {
			format := "  -%s=%s"
			if strings.Contains(fl.DefValue, " ") || fl.DefValue == "" {
				format = "  -%s=%q"
			}
			flagUsage := fmt.Sprintf(format+lineSep, fl.Name, fl.DefValue)

			// Format the usage text to not exceed maxLineLength characters per line.
			words := strings.SplitAfter(fl.Usage, " ")
			lineLength := len(lineSep) - 1
			for _, w := range words {
				if lineLength+len(w) > maxLineLength {
					flagUsage += lineSep
					lineLength = len(lineSep) - 1
				}
				flagUsage += w
				lineLength += len(w)
			}
			fmt.Fprintf(os.Stderr, "%s\n", flagUsage)
		}
	}
}

func main() {
	flag.CommandLine.Init(os.Args[0], flag.ContinueOnError)
	flag.CommandLine.Usage = usage

	if err := flag.CommandLine.Parse(os.Args[1:]); err != nil {
		if err != flag.ErrHelp {
			glog.Errorf("Invalid command line arguments. Help: %s -h", os.Args[0])
		}
		os.Exit(2)
	}

	if !strings.HasPrefix(*pathPrefix, "/") {
		*pathPrefix = "/" + *pathPrefix
	}
	if !strings.HasSuffix(*pathPrefix, "/") {
		*pathPrefix = *pathPrefix + "/"
	}

	versionInfoTmpl.Execute(os.Stdout, BuildInfo)

	if *printVersion {
		os.Exit(0)
	}

	p := NewPrometheus()
	registry.MustRegister(p)
	p.Serve()
}
<commit_msg>Remove special listing of config.file in usage<commit_after>// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"flag"
	"fmt"
	_ "net/http/pprof" // Comment this line to disable pprof endpoint.
	"os"
	"os/signal"
	"sort"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/golang/glog"

	clientmodel "github.com/prometheus/client_golang/model"
	registry "github.com/prometheus/client_golang/prometheus"

	"github.com/prometheus/prometheus/config"
	"github.com/prometheus/prometheus/notification"
	"github.com/prometheus/prometheus/retrieval"
	"github.com/prometheus/prometheus/rules/manager"
	"github.com/prometheus/prometheus/storage"
	"github.com/prometheus/prometheus/storage/local"
	"github.com/prometheus/prometheus/storage/remote"
	"github.com/prometheus/prometheus/storage/remote/influxdb"
	"github.com/prometheus/prometheus/storage/remote/opentsdb"
	"github.com/prometheus/prometheus/web"
	"github.com/prometheus/prometheus/web/api"
)

const deletionBatchSize = 100

// Commandline flags.
var (
	configFile = flag.String("config.file", "prometheus.conf", "Prometheus configuration file name.")

	alertmanagerURL           = flag.String("alertmanager.url", "", "The URL of the alert manager to send notifications to.")
	notificationQueueCapacity = flag.Int("alertmanager.notification-queue-capacity", 100, "The capacity of the queue for pending alert manager notifications.")

	persistenceStoragePath = flag.String("storage.local.path", "/tmp/metrics", "Base path for metrics storage.")

	opentsdbURL          = flag.String("storage.remote.opentsdb-url", "", "The URL of the remote OpenTSDB server to send samples to. None, if empty.")
	influxdbURL          = flag.String("storage.remote.influxdb-url", "", "The URL of the remote InfluxDB server to send samples to. None, if empty.")
	remoteStorageTimeout = flag.Duration("storage.remote.timeout", 30*time.Second, "The timeout to use when sending samples to the remote storage.")

	numMemoryChunks = flag.Int("storage.local.memory-chunks", 1024*1024, "How many chunks to keep in memory. While the size of a chunk is 1kiB, the total memory usage will be significantly higher than this value * 1kiB. Furthermore, for various reasons, more chunks might have to be kept in memory temporarily.")

	persistenceRetentionPeriod = flag.Duration("storage.local.retention", 15*24*time.Hour, "How long to retain samples in the local storage.")
	maxChunksToPersist         = flag.Int("storage.local.max-chunks-to-persist", 1024*1024, "How many chunks can be waiting for persistence before sample ingestion will stop. Many chunks waiting to be persisted will increase the checkpoint size.")

	checkpointInterval         = flag.Duration("storage.local.checkpoint-interval", 5*time.Minute, "The period at which the in-memory metrics and the chunks not yet persisted to series files are checkpointed.")
	checkpointDirtySeriesLimit = flag.Int("storage.local.checkpoint-dirty-series-limit", 5000, "If approx. that many time series are in a state that would require a recovery operation after a crash, a checkpoint is triggered, even if the checkpoint interval hasn't passed yet. A recovery operation requires a disk seek. The default limit intends to keep the recovery time below 1min even on spinning disks. With SSD, recovery is much faster, so you might want to increase this value in that case to avoid overly frequent checkpoints.")
	seriesSyncStrategy         = flag.String("storage.local.series-sync-strategy", "adaptive", "When to sync series files after modification. Possible values: 'never', 'always', 'adaptive'. Sync'ing slows down storage performance but reduces the risk of data loss in case of an OS crash. With the 'adaptive' strategy, series files are sync'd for as long as the storage is not too much behind on chunk persistence.")

	storageDirty          = flag.Bool("storage.local.dirty", false, "If set, the local storage layer will perform crash recovery even if the last shutdown appears to be clean.")
	storagePedanticChecks = flag.Bool("storage.local.pedantic-checks", false, "If set, a crash recovery will perform checks on each series file. This might take a very long time.")

	pathPrefix = flag.String("web.path-prefix", "/", "Prefix for all web paths.")

	printVersion = flag.Bool("version", false, "Print version information.")
)

type prometheus struct {
	ruleManager         manager.RuleManager
	targetManager       retrieval.TargetManager
	notificationHandler *notification.NotificationHandler
	storage             local.Storage
	remoteStorageQueues []*remote.StorageQueueManager

	webService *web.WebService

	closeOnce sync.Once
}

// NewPrometheus creates a new prometheus object based on flag values.
// Call Serve() to start serving and Close() for clean shutdown.
func NewPrometheus() *prometheus {
	conf, err := config.LoadFromFile(*configFile)
	if err != nil {
		glog.Errorf("Couldn't load configuration (-config.file=%s): %v\n", *configFile, err)
		os.Exit(2)
	}

	notificationHandler := notification.NewNotificationHandler(*alertmanagerURL, *notificationQueueCapacity)

	var syncStrategy local.SyncStrategy
	switch *seriesSyncStrategy {
	case "never":
		syncStrategy = local.Never
	case "always":
		syncStrategy = local.Always
	case "adaptive":
		syncStrategy = local.Adaptive
	default:
		glog.Errorf("Invalid flag value for 'storage.local.series-sync-strategy': %s\n", *seriesSyncStrategy)
		os.Exit(2)
	}

	o := &local.MemorySeriesStorageOptions{
		MemoryChunks:               *numMemoryChunks,
		MaxChunksToPersist:         *maxChunksToPersist,
		PersistenceStoragePath:     *persistenceStoragePath,
		PersistenceRetentionPeriod: *persistenceRetentionPeriod,
		CheckpointInterval:         *checkpointInterval,
		CheckpointDirtySeriesLimit: *checkpointDirtySeriesLimit,
		Dirty:          *storageDirty,
		PedanticChecks: *storagePedanticChecks,
		SyncStrategy:   syncStrategy,
	}
	memStorage, err := local.NewMemorySeriesStorage(o)
	if err != nil {
		glog.Error("Error opening memory series storage: ", err)
		os.Exit(1)
	}

	var sampleAppender storage.SampleAppender
	var remoteStorageQueues []*remote.StorageQueueManager
	if *opentsdbURL == "" && *influxdbURL == "" {
		glog.Warningf("No remote storage URLs provided; not sending any samples to long-term storage")
		sampleAppender = memStorage
	} else {
		fanout := storage.Fanout{memStorage}

		addRemoteStorage := func(c remote.StorageClient) {
			qm := remote.NewStorageQueueManager(c, 100*1024)
			fanout = append(fanout, qm)
			remoteStorageQueues = append(remoteStorageQueues, qm)
		}

		if *opentsdbURL != "" {
			addRemoteStorage(opentsdb.NewClient(*opentsdbURL, *remoteStorageTimeout))
		}
		if *influxdbURL != "" {
			addRemoteStorage(influxdb.NewClient(*influxdbURL, *remoteStorageTimeout))
		}

		sampleAppender = fanout
	}

	targetManager := retrieval.NewTargetManager(sampleAppender, conf.GlobalLabels())
	targetManager.AddTargetsFromConfig(conf)

	ruleManager := manager.NewRuleManager(&manager.RuleManagerOptions{
		SampleAppender:      sampleAppender,
		NotificationHandler: notificationHandler,
		EvaluationInterval:  conf.EvaluationInterval(),
		Storage:             memStorage,
		PrometheusURL:       web.MustBuildServerURL(*pathPrefix),
		PathPrefix:          *pathPrefix,
	})
	if err := ruleManager.AddRulesFromConfig(conf); err != nil {
		glog.Error("Error loading rule files: ", err)
		os.Exit(1)
	}

	flags := map[string]string{}
	flag.VisitAll(func(f *flag.Flag) {
		flags[f.Name] = f.Value.String()
	})
	prometheusStatus := &web.PrometheusStatusHandler{
		BuildInfo:   BuildInfo,
		Config:      conf.String(),
		RuleManager: ruleManager,
		TargetPools: targetManager.Pools(),
		Flags:       flags,
		Birth:       time.Now(),
		PathPrefix:  *pathPrefix,
	}

	alertsHandler := &web.AlertsHandler{
		RuleManager: ruleManager,
		PathPrefix:  *pathPrefix,
	}

	consolesHandler := &web.ConsolesHandler{
		Storage:    memStorage,
		PathPrefix: *pathPrefix,
	}

	graphsHandler := &web.GraphsHandler{
		PathPrefix: *pathPrefix,
	}

	metricsService := &api.MetricsService{
		Now:     clientmodel.Now,
		Storage: memStorage,
	}

	webService := &web.WebService{
		StatusHandler:   prometheusStatus,
		MetricsHandler:  metricsService,
		ConsolesHandler: consolesHandler,
		AlertsHandler:   alertsHandler,
		GraphsHandler:   graphsHandler,
	}

	p := &prometheus{
		ruleManager:         ruleManager,
		targetManager:       targetManager,
		notificationHandler: notificationHandler,
		storage:             memStorage,
		remoteStorageQueues: remoteStorageQueues,

		webService: webService,
	}
	webService.QuitChan = make(chan struct{})
	return p
}

// Serve starts the Prometheus server. It returns after the server has been shut
// down. The method installs an interrupt handler, allowing to trigger a
// shutdown by sending SIGTERM to the process.
func (p *prometheus) Serve() {
	for _, q := range p.remoteStorageQueues {
		go q.Run()
	}
	go p.ruleManager.Run()
	go p.notificationHandler.Run()

	p.storage.Start()

	go func() {
		err := p.webService.ServeForever(*pathPrefix)
		if err != nil {
			glog.Fatal(err)
		}
	}()

	notifier := make(chan os.Signal)
	signal.Notify(notifier, os.Interrupt, syscall.SIGTERM)
	select {
	case <-notifier:
		glog.Warning("Received SIGTERM, exiting gracefully...")
	case <-p.webService.QuitChan:
		glog.Warning("Received termination request via web service, exiting gracefully...")
	}

	p.targetManager.Stop()
	p.ruleManager.Stop()

	if err := p.storage.Stop(); err != nil {
		glog.Error("Error stopping local storage: ", err)
	}

	for _, q := range p.remoteStorageQueues {
		q.Stop()
	}

	p.notificationHandler.Stop()
	glog.Info("See you next time!")
}

// Describe implements registry.Collector.
func (p *prometheus) Describe(ch chan<- *registry.Desc) {
	p.notificationHandler.Describe(ch)
	p.storage.Describe(ch)
	for _, q := range p.remoteStorageQueues {
		q.Describe(ch)
	}
}

// Collect implements registry.Collector.
func (p *prometheus) Collect(ch chan<- registry.Metric) {
	p.notificationHandler.Collect(ch)
	p.storage.Collect(ch)
	for _, q := range p.remoteStorageQueues {
		q.Collect(ch)
	}
}

func usage() {
	groups := make(map[string][]*flag.Flag)
	// Set a default group for ungrouped flags.
	groups["."] = make([]*flag.Flag, 0)

	// Bucket flags into groups based on the first of their dot-separated levels.
	flag.VisitAll(func(fl *flag.Flag) {
		parts := strings.SplitN(fl.Name, ".", 2)
		if len(parts) == 1 {
			groups["."] = append(groups["."], fl)
		} else {
			name := parts[0]
			groups[name] = append(groups[name], fl)
		}
	})

	groupsOrdered := make(sort.StringSlice, 0, len(groups))
	for groupName := range groups {
		groupsOrdered = append(groupsOrdered, groupName)
	}
	sort.Sort(groupsOrdered)

	fmt.Fprintf(os.Stderr, "Usage: %s [options ...]:\n\n", os.Args[0])

	const (
		maxLineLength = 80
		lineSep       = "\n      "
	)
	for _, groupName := range groupsOrdered {
		if groupName != "." {
			fmt.Fprintf(os.Stderr, "\n%s:\n", strings.Title(groupName))
		}

		for _, fl := range groups[groupName] {
			format := "  -%s=%s"
			if strings.Contains(fl.DefValue, " ") || fl.DefValue == "" {
				format = "  -%s=%q"
			}
			flagUsage := fmt.Sprintf(format+lineSep, fl.Name, fl.DefValue)

			// Format the usage text to not exceed maxLineLength characters per line.
			words := strings.SplitAfter(fl.Usage, " ")
			lineLength := len(lineSep) - 1
			for _, w := range words {
				if lineLength+len(w) > maxLineLength {
					flagUsage += lineSep
					lineLength = len(lineSep) - 1
				}
				flagUsage += w
				lineLength += len(w)
			}
			fmt.Fprintf(os.Stderr, "%s\n", flagUsage)
		}
	}
}

func main() {
	flag.CommandLine.Init(os.Args[0], flag.ContinueOnError)
	flag.CommandLine.Usage = usage

	if err := flag.CommandLine.Parse(os.Args[1:]); err != nil {
		if err != flag.ErrHelp {
			glog.Errorf("Invalid command line arguments. Help: %s -h", os.Args[0])
		}
		os.Exit(2)
	}

	if !strings.HasPrefix(*pathPrefix, "/") {
		*pathPrefix = "/" + *pathPrefix
	}
	if !strings.HasSuffix(*pathPrefix, "/") {
		*pathPrefix = *pathPrefix + "/"
	}

	versionInfoTmpl.Execute(os.Stdout, BuildInfo)

	if *printVersion {
		os.Exit(0)
	}

	p := NewPrometheus()
	registry.MustRegister(p)
	p.Serve()
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"crypto/tls"
	"errors"
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/google/gops/agent"
	"github.com/nabeken/go-smtp-source/net/smtp"
	"golang.org/x/time/rate"
)

var (
	myDate     = time.Now()
	myPid      = os.Getpid()
	myhostname = "localhost"
)

var (
	defaultSender    = "from@example.com"
	defaultRecipient = "to@example.com"
	defaultSubject   = "from go-smtp-source"
)

var config *Config

type Config struct {
	Host   string
	Sender string

	Recipient      string
	RecipientCount int

	MessageCount int
	Sessions     int
	MessageSize  int
	Subject      string

	Verbose bool

	// extension
	UseTLS      bool
	ResolveOnce bool
	QPS         rate.Limit

	tlsConfig *tls.Config
}

func usage(m, def string) string {
	return fmt.Sprintf("%s [default: %s]", m, def)
}

func Parse() error {
	var (
		msgcount = flag.Int("m", 1, usage("specify a number of messages to send.", "1"))
		msgsize  = flag.Int("l", 0, usage("specify the size of the body.", "0"))
		session  = flag.Int("s", 1, usage("specify a number of cocurrent sessions.", "1"))
		sender   = flag.String("f", defaultSender, usage("specify a sender address.", defaultSender))
		subject  = flag.String("S", defaultSubject, usage("specify a subject.", defaultSubject))

		verbose = flag.Bool("v", false, usage("enable verbose mode.", "false"))

		recipient = flag.String("t", defaultRecipient, usage("specify a recipient address.", defaultRecipient))

		recipientCount = flag.Int(
			"r", 1,
			usage("specify the number of recipients to send per transaction. Recipient names are generated by prepending a number to the recipient address.", "1"),
		)

		usetls      = flag.Bool("tls", false, usage("specify if STARTTLS is needed.", "false"))
		resolveOnce = flag.Bool("resolve-once", false, usage("resolve the hostname only once.", "false"))

		qps = flag.Float64("q", 0, usage("specify a queries per second.", "no rate limit"))
	)

	flag.Parse()

	host := flag.Arg(0)
	if host == "" {
		return errors.New("host is missing")
	}

	config = &Config{
		Host:   host,
		Sender: *sender,

		Recipient:      *recipient,
		RecipientCount: *recipientCount,

		MessageCount: *msgcount,
		MessageSize:  *msgsize,
		Sessions:     *session,
		Subject:      *subject,

		Verbose: *verbose,

		UseTLS:      *usetls,
		ResolveOnce: *resolveOnce,

		QPS: rate.Limit(*qps),

		tlsConfig: &tls.Config{
			InsecureSkipVerify: true,
		},
	}
	return nil
}

type transaction struct {
	Sender     string
	Recipients []string
	TxIdx      int
}

func sendMail(c *smtp.Client, tx *transaction) error {
	if config.UseTLS {
		if err := c.StartTLS(config.tlsConfig); err != nil {
			return err
		}
	} else {
		if err := c.Hello(myhostname); err != nil {
			return err
		}
	}
	if err := c.Mail(config.Sender); err != nil {
		return err
	}

	for i := range tx.Recipients {
		if err := c.Rcpt(tx.Recipients[i]); err != nil {
			return err
		}
	}

	wc, err := c.Data()
	if err != nil {
		return err
	}

	fmt.Fprintf(wc, "From: <%s>\n", config.Sender)
	fmt.Fprintf(wc, "To: <%s>\n", config.Recipient)
	fmt.Fprintf(wc, "Date: %s\n", myDate.Format(time.RFC1123))

	subject := fmt.Sprintf(config.Subject, tx.TxIdx)
	if subjectIdx := strings.Index(subject, "%!(EXTRA"); subjectIdx >= 0 {
		fmt.Fprintf(wc, "Subject: %s\n", subject[0:subjectIdx])
	} else {
		fmt.Fprintf(wc, "Subject: %s\n", subject)
	}
	fmt.Fprintf(wc, "Message-Id: <%04x.%04x@%s>\n", myPid, config.MessageCount, myhostname)
	fmt.Fprintln(wc, "")

	if config.MessageSize == 0 {
		for i := 1; i < 5; i++ {
			fmt.Fprintf(wc, "La de da de da %d.\n", i)
		}
	} else {
		for i := 1; i < config.MessageSize; i++ {
			fmt.Fprint(wc, "X")
			if i%80 == 0 {
				fmt.Fprint(wc, "\n")
			}
		}
	}

	if err := wc.Close(); err != nil {
		return err
	}

	return c.Quit()
}

func calcNumTx(messageCount, recipientCount int) int {
	n := messageCount / recipientCount
	if rem := messageCount % recipientCount; rem > 0 {
		n++
	}
	return n
}

// txidx starts from 1.
func generateRecipients(rcpt string, txidx, recipientCount, nrcpt int) []string {
	pos := 1
	if txidx > 1 {
		pos += (txidx - 1) * recipientCount
	}

	recipients := make([]string, 0, nrcpt)
	for i := 0; i < nrcpt; i++ {
		recipients = append(recipients, fmt.Sprintf("%d_%s", pos, rcpt))
		pos++
	}

	return recipients
}

func main() {
	if err := agent.Listen(agent.Options{}); err != nil {
		log.Fatal(err)
	}
	if err := Parse(); err != nil {
		log.Fatal(err)
	}

	addr, port, err := net.SplitHostPort(config.Host)
	if err != nil {
		log.Fatal(err)
	}

	if config.ResolveOnce {
		addrs, err := net.LookupHost(addr)
		if err != nil {
			log.Fatal(err)
		}

		// use first one
		addr = addrs[0]
	}

	// semaphore for concurrency
	sem := make(chan struct{}, config.Sessions)
	for i := 0; i < config.Sessions; i++ {
		sem <- struct{}{}
	}

	// response for async dial
	type clientCall struct {
		c   *smtp.Client
		err error
		tx  *transaction
	}

	clientQueue := make(chan *clientCall, config.Sessions)
	ntx := calcNumTx(config.MessageCount, config.RecipientCount)

	go func() {
		for i := 0; i < ntx; i++ {
			txidx := i + 1
			conn, err := net.Dial("tcp", addr+":"+port)
			if err != nil {
				clientQueue <- &clientCall{nil, err, nil}
				continue
			}

			if tcpConn, ok := conn.(*net.TCPConn); ok {
				// smtp-source does this so we just follow it
				if err := tcpConn.SetLinger(0); err != nil {
					clientQueue <- &clientCall{nil, err, nil}
					continue
				}
			}

			nrcpt := config.RecipientCount

			// if we're the last, we do process the remainder
			if i == (ntx - 1) {
				if rem := config.MessageCount % config.RecipientCount; rem > 0 {
					nrcpt = rem
				}
			}

			tx := &transaction{
				Sender: config.Sender,
				TxIdx:  txidx,
			}

			if config.RecipientCount > 1 {
				tx.Recipients = generateRecipients(config.Recipient, txidx, config.RecipientCount, nrcpt)
			} else {
				tx.Recipients = []string{config.Recipient}
			}

			if config.Verbose {
				log.Printf("ntx:%d i:%d txidx:%d nrcpt:%d recipients:%v", ntx, i, txidx, nrcpt, tx.Recipients)
			}

			c, err := smtp.NewClient(conn, addr)
			clientQueue <- &clientCall{c, err, tx}
		}
	}()

	// wait group for all attempts
	var wg sync.WaitGroup
	wg.Add(ntx)

	limiter := rate.NewLimiter(rate.Inf, 0)
	if config.QPS > 0 {
		limiter = rate.NewLimiter(config.QPS, config.RecipientCount)
	}

	for i := 0; i < ntx; i++ {
		<-sem
		go func() {
			defer func() {
				sem <- struct{}{}
				wg.Done()
			}()

			cc := <-clientQueue
			if cc.err != nil {
				log.Println("unable to connect to the server:", cc.err)
				return
			}

			limiter.WaitN(context.TODO(), len(cc.tx.Recipients))
			if err := sendMail(cc.c, cc.tx); err != nil {
				log.Println("unable to send a mail:", err)
			}
		}()
	}

	wg.Wait()
}
<commit_msg>Quit the session outside of sendMail()<commit_after>package main

import (
	"context"
	"crypto/tls"
	"errors"
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/google/gops/agent"
	"github.com/nabeken/go-smtp-source/net/smtp"
	"golang.org/x/time/rate"
)

var (
	myDate     = time.Now()
	myPid      = os.Getpid()
	myhostname = "localhost"
)

var (
	defaultSender    = "from@example.com"
	defaultRecipient = "to@example.com"
	defaultSubject   = "from go-smtp-source"
)

var config *Config

type Config struct {
	Host   string
	Sender string

	Recipient      string
	RecipientCount int

	MessageCount int
	Sessions     int
	MessageSize  int
	Subject      string

	Verbose bool

	// extension
	UseTLS      bool
	ResolveOnce bool
	QPS         rate.Limit

	tlsConfig *tls.Config
}

func usage(m, def string) string {
	return fmt.Sprintf("%s [default: %s]", m, def)
}

func Parse() error {
	var (
		msgcount = flag.Int("m", 1, usage("specify a number of messages to send.", "1"))
		msgsize  = flag.Int("l", 0, usage("specify the size of the body.", "0"))
		session  = flag.Int("s", 1, usage("specify a number of cocurrent sessions.", "1"))
		sender   = flag.String("f", defaultSender, usage("specify a sender address.", defaultSender))
		subject  = flag.String("S", defaultSubject, usage("specify a subject.", defaultSubject))

		verbose = flag.Bool("v", false, usage("enable verbose mode.", "false"))

		recipient = flag.String("t", defaultRecipient, usage("specify a recipient address.", defaultRecipient))

		recipientCount = flag.Int(
			"r", 1,
			usage("specify the number of recipients to send per transaction. Recipient names are generated by prepending a number to the recipient address.", "1"),
		)

		usetls      = flag.Bool("tls", false, usage("specify if STARTTLS is needed.", "false"))
		resolveOnce = flag.Bool("resolve-once", false, usage("resolve the hostname only once.", "false"))

		qps = flag.Float64("q", 0, usage("specify a queries per second.", "no rate limit"))
	)

	flag.Parse()

	host := flag.Arg(0)
	if host == "" {
		return errors.New("host is missing")
	}

	config = &Config{
		Host:   host,
		Sender: *sender,

		Recipient:      *recipient,
		RecipientCount: *recipientCount,

		MessageCount: *msgcount,
		MessageSize:  *msgsize,
		Sessions:     *session,
		Subject:      *subject,

		Verbose: *verbose,

		UseTLS:      *usetls,
		ResolveOnce: *resolveOnce,

		QPS: rate.Limit(*qps),

		tlsConfig: &tls.Config{
			InsecureSkipVerify: true,
		},
	}
	return nil
}

type transaction struct {
	Sender     string
	Recipients []string
	TxIdx      int
}

func sendMail(c *smtp.Client, tx *transaction) error {
	if config.UseTLS {
		if err := c.StartTLS(config.tlsConfig); err != nil {
			return err
		}
	} else {
		if err := c.Hello(myhostname); err != nil {
			return err
		}
	}
	if err := c.Mail(config.Sender); err != nil {
		return err
	}

	for i := range tx.Recipients {
		if err := c.Rcpt(tx.Recipients[i]); err != nil {
			return err
		}
	}

	wc, err := c.Data()
	if err != nil {
		return err
	}

	fmt.Fprintf(wc, "From: <%s>\n", config.Sender)
	fmt.Fprintf(wc, "To: <%s>\n", config.Recipient)
	fmt.Fprintf(wc, "Date: %s\n", myDate.Format(time.RFC1123))

	subject := fmt.Sprintf(config.Subject, tx.TxIdx)
	if subjectIdx := strings.Index(subject, "%!(EXTRA"); subjectIdx >= 0 {
		fmt.Fprintf(wc, "Subject: %s\n", subject[0:subjectIdx])
	} else {
		fmt.Fprintf(wc, "Subject: %s\n", subject)
	}
	fmt.Fprintf(wc, "Message-Id: <%04x.%04x@%s>\n", myPid, config.MessageCount, myhostname)
	fmt.Fprintln(wc, "")

	if config.MessageSize == 0 {
		for i := 1; i < 5; i++ {
			fmt.Fprintf(wc, "La de da de da %d.\n", i)
		}
	} else {
		for i := 1; i < config.MessageSize; i++ {
			fmt.Fprint(wc, "X")
			if i%80 == 0 {
				fmt.Fprint(wc, "\n")
			}
		}
	}

	return wc.Close()
}

func calcNumTx(messageCount, recipientCount int) int {
	n := messageCount / recipientCount
	if rem := messageCount % recipientCount; rem > 0 {
		n++
	}
	return n
}

// txidx starts from 1.
func generateRecipients(rcpt string, txidx, recipientCount, nrcpt int) []string {
	pos := 1
	if txidx > 1 {
		pos += (txidx - 1) * recipientCount
	}

	recipients := make([]string, 0, nrcpt)
	for i := 0; i < nrcpt; i++ {
		recipients = append(recipients, fmt.Sprintf("%d_%s", pos, rcpt))
		pos++
	}

	return recipients
}

func main() {
	if err := agent.Listen(agent.Options{}); err != nil {
		log.Fatal(err)
	}
	if err := Parse(); err != nil {
		log.Fatal(err)
	}

	addr, port, err := net.SplitHostPort(config.Host)
	if err != nil {
		log.Fatal(err)
	}

	if config.ResolveOnce {
		addrs, err := net.LookupHost(addr)
		if err != nil {
			log.Fatal(err)
		}

		// use first one
		addr = addrs[0]
	}

	// semaphore for concurrency
	sem := make(chan struct{}, config.Sessions)
	for i := 0; i < config.Sessions; i++ {
		sem <- struct{}{}
	}

	// response for async dial
	type clientCall struct {
		c   *smtp.Client
		err error
		tx  *transaction
	}

	clientQueue := make(chan *clientCall, config.Sessions)
	ntx := calcNumTx(config.MessageCount, config.RecipientCount)

	go func() {
		for i := 0; i < ntx; i++ {
			txidx := i + 1
			conn, err := net.Dial("tcp", addr+":"+port)
			if err != nil {
				clientQueue <- &clientCall{nil, err, nil}
				continue
			}

			if tcpConn, ok := conn.(*net.TCPConn); ok {
				// smtp-source does this so we just follow it
				if err := tcpConn.SetLinger(0); err != nil {
					clientQueue <- &clientCall{nil, err, nil}
					continue
				}
			}

			nrcpt := config.RecipientCount

			// if we're the last, we do process the remainder
			if i == (ntx - 1) {
				if rem := config.MessageCount % config.RecipientCount; rem > 0 {
					nrcpt = rem
				}
			}

			tx := &transaction{
				Sender: config.Sender,
				TxIdx:  txidx,
			}

			if config.RecipientCount > 1 {
				tx.Recipients = generateRecipients(config.Recipient, txidx, config.RecipientCount, nrcpt)
			} else {
				tx.Recipients = []string{config.Recipient}
			}

			if config.Verbose {
				log.Printf("ntx:%d i:%d txidx:%d nrcpt:%d recipients:%v", ntx, i, txidx, nrcpt, tx.Recipients)
			}

			c, err := smtp.NewClient(conn, addr)
			clientQueue <- &clientCall{c, err, tx}
		}
	}()

	// wait group for all attempts
	var wg sync.WaitGroup
	wg.Add(ntx)

	limiter := rate.NewLimiter(rate.Inf, 0)
	if config.QPS > 0 {
		limiter = rate.NewLimiter(config.QPS, config.RecipientCount)
	}

	for i := 0; i < ntx; i++ {
		<-sem
		go func() {
			defer func() {
				sem <- struct{}{}
				wg.Done()
			}()

			cc := <-clientQueue
			if cc.err != nil {
				log.Println("unable to connect to the server:", cc.err)
				return
			}

			limiter.WaitN(context.TODO(), len(cc.tx.Recipients))
			if err := sendMail(cc.c, cc.tx); err != nil {
				log.Println("unable to send a mail:", err)
			}

			if err := cc.c.Quit(); err != nil {
				log.Println("unable to quit a session:", err)
			}
		}()
	}

	wg.Wait()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"sort"
	"strconv"
	"time"

	"github.com/nsf/termbox-go"
)

func main() {
	err := termbox.Init()
	if err != nil {
		fmt.Fprintln(os.Stderr, "Unable to initialize termbox.")
		os.Exit(1)
	}
	defer termbox.Close()

	events := make(chan termbox.Event)
	go func() {
		for {
			events <- termbox.PollEvent()
		}
	}()

Loop:
	for {
		processes := getRunningProcesses()
		sort.Sort(ByPid(processes))
		drawProcessList(processes)

		select {
		case <-time.After(time.Second):
			// nothing, redraw on next iteration

		case ev := <-events:
			if ev.Type == termbox.EventKey {
				if ev.Ch == 'q' {
					break Loop
				}

				// TODO: handle other user input
			}
		}
	}
}

func drawProcessList(processes []Process) {
	termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)

	y := 0
	x := 0

	// 32768 is the max pid on my system, so ensure the column is at least 5 wide
	pidColumnTitle := "PID"
	pidColumnWidth := len(pidColumnTitle) + 2

	commandColumnTitle := "Command"

	// spaces to right align pid title
	for i := 0; i < pidColumnWidth-len(pidColumnTitle); i++ {
		setTitleCell(&x, y, ' ', termbox.ColorCyan)
	}

	// pid title
	for _, ch := range pidColumnTitle {
		setTitleCell(&x, y, ch, termbox.ColorCyan)
	}

	// space to separate column
	setTitleCell(&x, y, ' ', termbox.ColorCyan)

	// command title
	for _, ch := range commandColumnTitle {
		setTitleCell(&x, y, ch, termbox.ColorGreen)
	}

	// finish header background
	w, _ := termbox.Size()
	for x < w {
		setTitleCell(&x, y, ' ', termbox.ColorGreen)
	}

	y++

	for _, process := range processes {
		x = 0
		strPid := strconv.Itoa(process.Pid)
		pidLength := len(strPid)

		// spaces to right align pid
		for i := 0; i < pidColumnWidth-pidLength; i++ {
			setCell(&x, y, ' ')
		}

		// pid
		for _, ch := range strPid {
			setCell(&x, y, ch)
		}

		// space to separate column
		setCell(&x, y, ' ')

		// command
		for _, ch := range process.Command {
			setCell(&x, y, ch)
		}

		y++
	}

	termbox.Flush()
}

func setTitleCell(x *int, y int, ch rune, bg termbox.Attribute) {
	termbox.SetCell(*x, y, ch, termbox.ColorBlack, bg)
	*x++
}

func setCell(x *int, y int, ch rune) {
	termbox.SetCell(*x, y, ch, termbox.ColorDefault, termbox.ColorDefault)
	*x++
}
<commit_msg>Basic process selection and scrolling<commit_after>package main

import (
	"fmt"
	"os"
	"sort"
	"strconv"
	"time"

	"github.com/nsf/termbox-go"
)

const (
	headerRows = 1
)

var (
	startIndex    = 0
	selectedIndex = 0
)

func main() {
	err := termbox.Init()
	if err != nil {
		fmt.Fprintln(os.Stderr, "Unable to initialize termbox.")
		os.Exit(1)
	}
	defer termbox.Close()

	events := make(chan termbox.Event)
	go func() {
		for {
			events <- termbox.PollEvent()
		}
	}()

	for {
		processes := getRunningProcesses()
		sort.Sort(ByPid(processes))
		drawProcessList(processes)

		select {
		case <-time.After(time.Second):
			// nothing, redraw on next iteration

		case ev := <-events:
			if ev.Type == termbox.EventKey {
				switch {
				case ev.Ch == 'q':
					return

				case ev.Ch == 'j' || ev.Key == termbox.KeyArrowDown:
					_, height := termbox.Size()
					numProcessRows := height - headerRows
					if selectedIndex+1 != numProcessRows {
						// not at bottom of ui
						selectedIndex++
					} else if len(processes)-startIndex > numProcessRows {
						// at bottom of ui and there's more processes to show,
						// scroll down
						startIndex++
					}

				case ev.Ch == 'k' || ev.Key == termbox.KeyArrowUp:
					if selectedIndex != 0 {
						// not at top of ui
						selectedIndex--
					} else if startIndex > 0 {
						// at top of ui and there's more processes to show,
						// scroll up
						startIndex--
					}
				}
			}
		}
	}
}

func drawProcessList(processes []Process) {
	termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
	width, height := termbox.Size()

	y := 0
	x := 0

	// 32768 is the max pid on my system, so ensure the column is at least 5 wide
	pidColumnTitle := "PID"
	pidColumnWidth := len(pidColumnTitle) + 2

	commandColumnTitle := "Command"

	// spaces to right align pid title
	for i := 0; i < pidColumnWidth-len(pidColumnTitle); i++ {
		setTitleCell(&x, y, ' ', termbox.ColorCyan)
	}

	// pid title
	for _, ch := range pidColumnTitle {
		setTitleCell(&x, y, ch, termbox.ColorCyan)
	}

	// space to separate column
	setTitleCell(&x, y, ' ', termbox.ColorCyan)

	// command title
	for _, ch := range commandColumnTitle {
		setTitleCell(&x, y, ch, termbox.ColorGreen)
	}

	// finish header background
	for x < width {
		setTitleCell(&x, y, ' ', termbox.ColorGreen)
	}

	y++

	displayProcesses := processes[startIndex : startIndex+height]
	if startIndex+height > len(processes) {
		displayProcesses = processes[startIndex:len(processes)]
	}

	for i, process := range displayProcesses {
		x = 0
		strPid := strconv.Itoa(process.Pid)

		fg := termbox.ColorDefault
		bg := termbox.ColorDefault

		if i == selectedIndex {
			fg = termbox.ColorBlack
			bg = termbox.ColorCyan
		}

		// spaces to right align pid
		for i := 0; i < pidColumnWidth-len(strPid); i++ {
			setCell(&x, y, ' ', fg, bg)
		}

		// pid
		for _, ch := range strPid {
			setCell(&x, y, ch, fg, bg)
		}

		// space to separate column
		setCell(&x, y, ' ', fg, bg)

		// command
		for _, ch := range process.Command {
			setCell(&x, y, ch, fg, bg)
		}

		if i == selectedIndex {
			// finish row background
			for x < width {
				setCell(&x, y, ' ', fg, bg)
			}
		}

		y++
	}

	termbox.Flush()
}

func setTitleCell(x *int, y int, ch rune, bg termbox.Attribute) {
	termbox.SetCell(*x, y, ch, termbox.ColorBlack, bg)
	*x++
}

func setCell(x *int, y int, ch rune, fg, bg termbox.Attribute) {
	termbox.SetCell(*x, y, ch, fg, bg)
	*x++
}
<|endoftext|>


<commit_before>package mpawselasticsearch

import (
	"errors"
	"flag"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cloudwatch"
	mp "github.com/mackerelio/go-mackerel-plugin"
)

const (
	nameSpace          = "AWS/ES"
	metricsTypeAverage = "Average"
	metricsTypeSum     = "Sum"
	metricsTypeMaximum = "Maximum"
	metricsTypeMinimum = "Minimum"
)

var graphdef = map[string]mp.Graphs{
	"es.ClusterStatus": {
		Label: "AWS ES ClusterStatus",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "ClusterStatus.green", Label: "green"},
			{Name: "ClusterStatus.yellow", Label: "yellow"},
			{Name: "ClusterStatus.red", Label: "red"},
		},
	},
	"es.Nodes": {
		Label: "AWS ES Nodes",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "Nodes", Label: "Nodes"},
		},
	},
	"es.SearchableDocuments": {
		Label: "AWS ES SearchableDocuments",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "SearchableDocuments", Label: "SearchableDocuments"},
		},
	},
	"es.DeletedDocuments": {
		Label: "AWS ES DeletedDocuments",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "DeletedDocuments", Label: "DeletedDocuments"},
		},
	},
	"es.CPUUtilization": {
		Label: "AWS ES CPU Utilization",
		Unit:  "percentage",
		Metrics: []mp.Metrics{
			{Name: "CPUUtilization", Label: "CPUUtilization"},
		},
	},
	"es.FreeStorageSpace": {
		Label: "AWS ES Free Storage Space",
		Unit:  "bytes",
		Metrics: []mp.Metrics{
			{Name: "FreeStorageSpace", Label: "FreeStorageSpace"},
		},
	},
	"es.ClusterUsedSpace": {
		Label: "AWS ES Cluster Used Space",
		Unit:  "bytes",
		Metrics: []mp.Metrics{
			{Name: "ClusterUsedSpace", Label: "ClusterUsedSpace"},
		},
	},
	"es.ClusterIndexWritesBlocked": {
		Label: "AWS ES ClusterIndexWritesBlocked",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "ClusterIndexWritesBlocked", Label: "ClusterIndexWritesBlocked"},
		},
	},
	"es.JVMMemoryPressure": {
		Label: "AWS ES JVMMemoryPressure",
		Unit:  "percentage",
		Metrics: []mp.Metrics{
			{Name: "JVMMemoryPressure", Label: "JVMMemoryPressure"},
		},
	},
	"es.AutomatedSnapshotFailure": {
		Label: "AWS ES AutomatedSnapshotFailure",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "AutomatedSnapshotFailure", Label: "AutomatedSnapshotFailure"},
		},
	},
	"es.MasterCPUUtilization": {
		Label: "AWS ES MasterCPUUtilization",
		Unit:  "percentage",
		Metrics: []mp.Metrics{
			{Name: "MasterCPUUtilization", Label: "MasterCPUUtilization"},
		},
	},
	"es.MasterFreeStorageSpace": {
		Label: "AWS ES MasterFreeStorageSpace",
		Unit:  "bytes",
		Metrics: []mp.Metrics{
			{Name: "MasterFreeStorageSpace", Label: "MasterFreeStorageSpace"},
		},
	},
	"es.MasterJVMMemoryPressure": {
		Label: "AWS ES MasterJVMMemoryPressure",
		Unit:  "percentage",
		Metrics: []mp.Metrics{
			{Name: "MasterJVMMemoryPressure", Label: "MasterJVMMemoryPressure"},
		},
	},
	"es.Latency": {
		Label: "AWS ES Latency",
		Unit:  "float",
		Metrics: []mp.Metrics{
			{Name: "ReadLatency", Label: "ReadLatency"},
			{Name: "WriteLatency", Label: "WriteLatency"},
		},
	},
	"es.Throughput": {
		Label: "AWS ES Throughput",
		Unit:  "bytes/sec",
		Metrics: []mp.Metrics{
			{Name: "ReadThroughput", Label: "ReadThroughput"},
			{Name: "WriteThroughput", Label: "WriteThroughput"},
		},
	},
	"es.DiskQueueDepth": {
		Label: "AWS ES DiskQueueDepth",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "DiskQueueDepth", Label: "DiskQueueDepth"},
		},
	},
	"es.IOPS": {
		Label: "AWS ES IOPS",
		Unit:  "iops",
		Metrics: []mp.Metrics{
			{Name: "ReadIOPS", Label: "ReadIOPS"},
			{Name: "WriteIOPS", Label: "WriteIOPS"},
		},
	},
}

type metrics struct {
	Name string
	Type string
}

// ESPlugin mackerel plugin for aws elasticsearch
type ESPlugin struct {
	Region          string
	AccessKeyID     string
	SecretAccessKey string
	Domain          string
	ClientID        string
	CloudWatch      *cloudwatch.CloudWatch
}

func (p *ESPlugin) prepare() error {
	sess, err := session.NewSession()
	if err != nil {
		return err
	}

	config := aws.NewConfig()
	if p.AccessKeyID != "" && p.SecretAccessKey != "" {
		config = config.WithCredentials(credentials.NewStaticCredentials(p.AccessKeyID, p.SecretAccessKey, ""))
	}
	if p.Region != "" {
		config = config.WithRegion(p.Region)
	}

	p.CloudWatch = cloudwatch.New(sess, config)
	return nil
}

func (p ESPlugin) getLastPoint(metric metrics) (float64, error) {
	now := time.Now()

	dimensions := []*cloudwatch.Dimension{
		{
			Name:  aws.String("DomainName"),
			Value: aws.String(p.Domain),
		},
		{
			Name:  aws.String("ClientId"),
			Value: aws.String(p.ClientID),
		},
	}

	response, err := p.CloudWatch.GetMetricStatistics(&cloudwatch.GetMetricStatisticsInput{
		Dimensions: dimensions,
		StartTime:  aws.Time(now.Add(time.Duration(180) * time.Second * -1)),
		EndTime:    aws.Time(now),
		MetricName: aws.String(metric.Name),
		Period:     aws.Int64(60),
		Statistics: []*string{aws.String(metric.Type)},
		Namespace:  aws.String(nameSpace),
	})

	if err != nil {
		return 0, err
	}

	datapoints := response.Datapoints
	if len(datapoints) == 0 {
		return 0, errors.New("fetched no datapoints")
	}

	// get a least recently datapoint
	// because a most recently datapoint is not stable.
	least := time.Now()
	var latestVal float64
	for _, dp := range datapoints {
		if dp.Timestamp.Before(least) {
			least = *dp.Timestamp
			if metric.Type == metricsTypeAverage {
				latestVal = *dp.Average
			} else if metric.Type == metricsTypeSum {
				latestVal = *dp.Sum
			} else if metric.Type == metricsTypeMaximum {
				latestVal = *dp.Maximum
			} else if metric.Type == metricsTypeMinimum {
				latestVal = *dp.Minimum
			}
		}
	}

	return latestVal, nil
}

// FetchMetrics interface for mackerelplugin
func (p ESPlugin) FetchMetrics() (map[string]float64, error) {
	stat := make(map[string]float64)

	for _, met := range [...]metrics{
		{Name: "ClusterStatus.green", Type: metricsTypeMinimum},
		{Name: "ClusterStatus.yellow", Type: metricsTypeMaximum},
		{Name: "ClusterStatus.red", Type: metricsTypeMaximum},
		{Name: "Nodes", Type: metricsTypeAverage},
		{Name: "SearchableDocuments", Type: metricsTypeAverage},
		{Name: "DeletedDocuments", Type: metricsTypeAverage},
		{Name: "CPUUtilization", Type: metricsTypeMaximum},
		{Name: "FreeStorageSpace", Type: metricsTypeMinimum},
		{Name: "ClusterUsedSpace", Type: metricsTypeMinimum},
		{Name: "ClusterIndexWritesBlocked", Type: metricsTypeMaximum},
		{Name: "JVMMemoryPressure", Type: metricsTypeMaximum},
		{Name: "AutomatedSnapshotFailure", Type: metricsTypeMaximum},
		{Name: "MasterCPUUtilization", Type: metricsTypeMaximum},
		{Name: "MasterFreeStorageSpace", Type: metricsTypeSum},
		{Name: "MasterJVMMemoryPressure", Type: metricsTypeMaximum},
		{Name: "ReadLatency", Type: metricsTypeAverage},
		{Name: "WriteLatency", Type: metricsTypeAverage},
		{Name: "ReadThroughput", Type: metricsTypeSum},
		{Name: "WriteThroughput", Type: metricsTypeSum},
		{Name: "DiskQueueDepth", Type: metricsTypeAverage},
		{Name: "ReadIOPS", Type: metricsTypeSum},
		{Name: "WriteIOPS", Type: metricsTypeSum},
	} {
		v, err := p.getLastPoint(met)
		if err == nil {
			if met.Name == "ClusterUsedSpace" || met.Name == "MasterFreeStorageSpace" || met.Name == "FreeStorageSpace" {
				// MBytes -> Bytes
				v = v * 1024 * 1024
			}
			stat[met.Name] = v
		} else {
			log.Printf("%s: %s", met, err)
		}
	}

	return stat, nil
}

// GraphDefinition interface for mackerelplugin
func (p ESPlugin) GraphDefinition() map[string]mp.Graphs {
	return graphdef
}

// Do the plugin
func Do() {
	optRegion := flag.String("region", "", "AWS Region")
	optAccessKeyID := flag.String("access-key-id", "", "AWS Access Key ID")
	optSecretAccessKey := flag.String("secret-access-key", "", "AWS Secret Access Key")
	optClientID := flag.String("client-id", "", "AWS Client ID")
	optDomain := flag.String("domain", "", "ES domain name")
	optTempfile := flag.String("tempfile", "", "Temp file name")
	flag.Parse()

	var es ESPlugin

	if *optRegion == "" {
		ec2metadata := ec2metadata.New(session.New())
		if ec2metadata.Available() {
			es.Region, _ = ec2metadata.Region()
		}
	} else {
		es.Region = *optRegion
	}

	es.Region = *optRegion
	es.Domain = *optDomain
	es.ClientID = *optClientID
	es.AccessKeyID = *optAccessKeyID
	es.SecretAccessKey = *optSecretAccessKey

	err := es.prepare()
	if err != nil {
		log.Fatalln(err)
	}

	helper := mp.NewMackerelPlugin(es)
	helper.Tempfile = *optTempfile

	helper.Run()
}
<commit_msg>Add KibanaHealthyNodes metric<commit_after>package mpawselasticsearch

import (
	"errors"
	"flag"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cloudwatch"
	mp "github.com/mackerelio/go-mackerel-plugin"
)

const (
	nameSpace          = "AWS/ES"
	metricsTypeAverage = "Average"
	metricsTypeSum     = "Sum"
	metricsTypeMaximum = "Maximum"
	metricsTypeMinimum = "Minimum"
)

var graphdef = map[string]mp.Graphs{
	"es.ClusterStatus": {
		Label: "AWS ES ClusterStatus",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "ClusterStatus.green", Label: "green"},
			{Name: "ClusterStatus.yellow", Label: "yellow"},
			{Name: "ClusterStatus.red", Label: "red"},
		},
	},
	"es.Nodes": {
		Label: "AWS ES Nodes",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "Nodes", Label: "Nodes"},
		},
	},
	"es.SearchableDocuments": {
		Label: "AWS ES SearchableDocuments",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "SearchableDocuments", Label: "SearchableDocuments"},
		},
	},
	"es.DeletedDocuments": {
		Label: "AWS ES DeletedDocuments",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "DeletedDocuments", Label: "DeletedDocuments"},
		},
	},
	"es.CPUUtilization": {
		Label: "AWS ES CPU Utilization",
		Unit:  "percentage",
		Metrics: []mp.Metrics{
			{Name: "CPUUtilization", Label: "CPUUtilization"},
		},
	},
	"es.FreeStorageSpace": {
		Label: "AWS ES Free Storage Space",
		Unit:  "bytes",
		Metrics: []mp.Metrics{
			{Name: "FreeStorageSpace", Label: "FreeStorageSpace"},
		},
	},
	"es.ClusterUsedSpace": {
		Label: "AWS ES Cluster Used Space",
		Unit:  "bytes",
		Metrics: []mp.Metrics{
			{Name: "ClusterUsedSpace", Label: "ClusterUsedSpace"},
		},
	},
	"es.ClusterIndexWritesBlocked": {
		Label: "AWS ES ClusterIndexWritesBlocked",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "ClusterIndexWritesBlocked", Label: "ClusterIndexWritesBlocked"},
		},
	},
	"es.JVMMemoryPressure": {
		Label: "AWS ES JVMMemoryPressure",
		Unit:  "percentage",
		Metrics: []mp.Metrics{
			{Name: "JVMMemoryPressure", Label: "JVMMemoryPressure"},
		},
	},
	"es.AutomatedSnapshotFailure": {
		Label: "AWS ES AutomatedSnapshotFailure",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "AutomatedSnapshotFailure", Label: "AutomatedSnapshotFailure"},
		},
	},
	"es.KibanaHealthyNodes": {
		Label: "AWS ES KibanaHealthyNodes",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "KibanaHealthyNodes", Label: "KibanaHealthyNodes"},
		},
	},
	"es.MasterCPUUtilization": {
		Label: "AWS ES MasterCPUUtilization",
		Unit:  "percentage",
		Metrics: []mp.Metrics{
			{Name: "MasterCPUUtilization", Label: "MasterCPUUtilization"},
		},
	},
	"es.MasterFreeStorageSpace": {
		Label: "AWS ES MasterFreeStorageSpace",
		Unit:  "bytes",
		Metrics: []mp.Metrics{
			{Name: "MasterFreeStorageSpace", Label: "MasterFreeStorageSpace"},
		},
	},
	"es.MasterJVMMemoryPressure": {
		Label: "AWS ES MasterJVMMemoryPressure",
		Unit:  "percentage",
		Metrics: []mp.Metrics{
			{Name: "MasterJVMMemoryPressure", Label: "MasterJVMMemoryPressure"},
		},
	},
	"es.Latency": {
		Label: "AWS ES Latency",
		Unit:  "float",
		Metrics: []mp.Metrics{
			{Name: "ReadLatency", Label: "ReadLatency"},
			{Name: "WriteLatency", Label: "WriteLatency"},
		},
	},
	"es.Throughput": {
		Label: "AWS ES Throughput",
		Unit:  "bytes/sec",
		Metrics: []mp.Metrics{
			{Name: "ReadThroughput", Label: "ReadThroughput"},
			{Name: "WriteThroughput", Label: "WriteThroughput"},
		},
	},
	"es.DiskQueueDepth": {
		Label: "AWS ES DiskQueueDepth",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "DiskQueueDepth", Label: "DiskQueueDepth"},
		},
	},
	"es.IOPS": {
		Label: "AWS ES IOPS",
		Unit:  "iops",
		Metrics: []mp.Metrics{
			{Name: "ReadIOPS", Label: "ReadIOPS"},
			{Name: "WriteIOPS", Label: "WriteIOPS"},
		},
	},
}

type metrics struct {
	Name string
	Type string
}

// ESPlugin mackerel plugin for aws elasticsearch
type ESPlugin struct {
	Region          string
	AccessKeyID     string
	SecretAccessKey string
	Domain          string
	ClientID        string
	CloudWatch      *cloudwatch.CloudWatch
}

func (p *ESPlugin) prepare() error {
	sess, err := session.NewSession()
	if err != nil {
		return err
	}

	config := aws.NewConfig()
	if p.AccessKeyID != "" && p.SecretAccessKey != "" {
		config = config.WithCredentials(credentials.NewStaticCredentials(p.AccessKeyID, p.SecretAccessKey, ""))
	}
	if p.Region != "" {
		config = config.WithRegion(p.Region)
	}

	p.CloudWatch = cloudwatch.New(sess, config)
	return nil
}

func (p ESPlugin) getLastPoint(metric metrics) (float64, error) {
	now := time.Now()

	dimensions := []*cloudwatch.Dimension{
		{
			Name:  aws.String("DomainName"),
			Value: aws.String(p.Domain),
		},
		{
			Name:  aws.String("ClientId"),
			Value: aws.String(p.ClientID),
		},
	}

	response, err := p.CloudWatch.GetMetricStatistics(&cloudwatch.GetMetricStatisticsInput{
		Dimensions: dimensions,
		StartTime:  aws.Time(now.Add(time.Duration(180) * time.Second * -1)),
		EndTime:    aws.Time(now),
		MetricName: aws.String(metric.Name),
		Period:     aws.Int64(60),
		Statistics: []*string{aws.String(metric.Type)},
		Namespace:  aws.String(nameSpace),
	})

	if err != nil {
		return 0, err
	}

	datapoints := response.Datapoints
	if len(datapoints) == 0 {
		return 0, errors.New("fetched no datapoints")
	}

	// get a least recently datapoint
	// because a most recently datapoint is not stable.
	least := time.Now()
	var latestVal float64
	for _, dp := range datapoints {
		if dp.Timestamp.Before(least) {
			least = *dp.Timestamp
			if metric.Type == metricsTypeAverage {
				latestVal = *dp.Average
			} else if metric.Type == metricsTypeSum {
				latestVal = *dp.Sum
			} else if metric.Type == metricsTypeMaximum {
				latestVal = *dp.Maximum
			} else if metric.Type == metricsTypeMinimum {
				latestVal = *dp.Minimum
			}
		}
	}

	return latestVal, nil
}

// FetchMetrics interface for mackerelplugin
func (p ESPlugin) FetchMetrics() (map[string]float64, error) {
	stat := make(map[string]float64)

	for _, met := range [...]metrics{
		{Name: "ClusterStatus.green", Type: metricsTypeMinimum},
		{Name: "ClusterStatus.yellow", Type: metricsTypeMaximum},
		{Name: "ClusterStatus.red", Type: metricsTypeMaximum},
		{Name: "Nodes", Type: metricsTypeAverage},
		{Name: "SearchableDocuments", Type: metricsTypeAverage},
		{Name: "DeletedDocuments", Type: metricsTypeAverage},
		{Name: "CPUUtilization", Type: metricsTypeMaximum},
		{Name: "FreeStorageSpace", Type: metricsTypeMinimum},
		{Name: "ClusterUsedSpace", Type: metricsTypeMinimum},
		{Name: "ClusterIndexWritesBlocked", Type: metricsTypeMaximum},
		{Name: "JVMMemoryPressure", Type: metricsTypeMaximum},
		{Name: "AutomatedSnapshotFailure", Type: metricsTypeMaximum},
		{Name: "KibanaHealthyNodes", Type: metricsTypeMinimum},
		{Name: "MasterCPUUtilization", Type: metricsTypeMaximum},
		{Name: "MasterFreeStorageSpace", Type: metricsTypeSum},
		{Name: "MasterJVMMemoryPressure", Type: metricsTypeMaximum},
		{Name: "ReadLatency", Type: metricsTypeAverage},
		{Name: "WriteLatency", Type: metricsTypeAverage},
		{Name: "ReadThroughput", Type: metricsTypeSum},
		{Name: "WriteThroughput", Type: metricsTypeSum},
		{Name: "DiskQueueDepth", Type: metricsTypeAverage},
		{Name: "ReadIOPS", Type: metricsTypeSum},
		{Name: "WriteIOPS", Type: metricsTypeSum},
	} {
		v, err := p.getLastPoint(met)
		if err == nil {
			if met.Name == "ClusterUsedSpace" || met.Name == "MasterFreeStorageSpace" || met.Name == "FreeStorageSpace" {
				// MBytes -> Bytes
				v = v * 1024 * 1024
			}
			stat[met.Name] = v
		} else {
			log.Printf("%s: %s", met, err)
		}
	}

	return stat, nil
}

// GraphDefinition interface for mackerelplugin
func (p ESPlugin) GraphDefinition() map[string]mp.Graphs {
	return graphdef
}

// Do the plugin
func Do() {
	optRegion := flag.String("region", "", "AWS Region")
	optAccessKeyID := flag.String("access-key-id", "", "AWS Access Key ID")
	optSecretAccessKey := flag.String("secret-access-key", "", "AWS Secret Access Key")
	optClientID := flag.String("client-id", "", "AWS Client ID")
	optDomain := flag.String("domain", "", "ES domain name")
	optTempfile := flag.String("tempfile", "", "Temp file name")
	flag.Parse()

	var es ESPlugin

	if *optRegion == "" {
		ec2metadata := ec2metadata.New(session.New())
		if ec2metadata.Available() {
			es.Region, _ = ec2metadata.Region()
		}
	} else {
		es.Region = *optRegion
	}

	es.Region = *optRegion
	es.Domain = *optDomain
	es.ClientID = *optClientID
	es.AccessKeyID = *optAccessKeyID
	es.SecretAccessKey = *optSecretAccessKey

	err := es.prepare()
	if err != nil {
		log.Fatalln(err)
	}

	helper := mp.NewMackerelPlugin(es)
	helper.Tempfile = *optTempfile

	helper.Run()
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sqs"
	"github.com/cbroglie/mustache"
	"github.com/ninjasphere/go-samsung-tv"
)

var commands = map[string]string{
	"power":       `<YAMAHA_AV cmd="PUT"><Main_Zone><Power_Control><Power>{{Value}}</Power></Power_Control></Main_Zone></YAMAHA_AV>`,
	"volume":      `<YAMAHA_AV cmd="PUT"><Main_Zone><Volume><Lvl><Val>{{Value}} {{Value2}} dB</Val><Exp></Exp><Unit></Unit></Lvl></Volume></Main_Zone></YAMAHA_AV>`, // <Val>Up 1 dB</Val>
	"volumeLevel": `<YAMAHA_AV cmd="PUT"><Main_Zone><Volume><Lvl><Val>{{Value}}</Val><Exp>1</Exp><Unit>dB</Unit></Lvl></Volume></Main_Zone></YAMAHA_AV>`,
	"mute":        `<YAMAHA_AV cmd="PUT"><Main_Zone><Volume><Mute>{{Value}}</Mute></Volume></Main_Zone></YAMAHA_AV>`,
	"input":       `<YAMAHA_AV cmd="PUT"><Main_Zone><Input><Input_Sel>{{Value}}</Input_Sel></Input></Main_Zone></YAMAHA_AV>`,
	"mode":        `<YAMAHA_AV cmd="PUT"><Main_Zone><Surround><Program_Sel><Current><Sound_Program>{{Value}}</Sound_Program></Current></Program_Sel></Surround></Main_Zone></YAMAHA_AV>`,
}

func main() {
	sess, err := session.NewSession(&aws.Config{
		Region:      aws.String(os.Getenv("AWS_REGION")),
		Credentials: credentials.NewSharedCredentials(os.Getenv("AWS_SHARED_CREDENTIALS_FILE"), os.Getenv("AWS_PROFILE")),
	})

	if err != nil {
		fmt.Println("session error: : %s", err)
		return
	}

	_, err = sess.Config.Credentials.Get()
	if err != nil {
		sess, err = session.NewSession(&aws.Config{
			Region: aws.String(os.Getenv("AWS_REGION")),
		})

		if err != nil {
			fmt.Println("session error: : %s", err)
			return
		}
	}

	_, err = sess.Config.Credentials.Get()
	if err != nil {
		fmt.Printlnn(`unable to establish aws credentials: %v`, err.Error())
		return
	}

	svc := sqs.New(sess)
	qURL := os.Getenv("AWS_SQS_URL")

	for {
		sqsRequest(svc, qURL)
	}
}

func sqsRequest(svc *sqs.SQS, qURL string) {
	result, err := svc.ReceiveMessage(&sqs.ReceiveMessageInput{
		AttributeNames: []*string{
			aws.String(sqs.MessageSystemAttributeNameSentTimestamp),
		},
		MessageAttributeNames: []*string{
			aws.String(sqs.QueueAttributeNameAll),
		},
		QueueUrl:            &qURL,
		MaxNumberOfMessages: aws.Int64(1),
		VisibilityTimeout:   aws.Int64(0),
		WaitTimeSeconds:     aws.Int64(20),
	})

	if err != nil {
		fmt.Println("sqs error: %s", err)
		return
	}

	if len(result.Messages) == 0 {
		fmt.Println("Received no messages")
		return
	}

	sendReciever(result)
	removeFromQueue(svc, qURL, result)
}

func removeFromQueue(svc *sqs.SQS, qURL string, result *sqs.ReceiveMessageOutput) {
	_, err := svc.DeleteMessage(&sqs.DeleteMessageInput{
		QueueUrl:      &qURL,
		ReceiptHandle: result.Messages[0].ReceiptHandle,
	})

	if err != nil {
		fmt.Println("delete error: %s", err)
		return
	}
}

func sendReciever(result *sqs.ReceiveMessageOutput) {
	type message struct {
		Action string
		Value  string
		Value2 int
	}
	var command message

	err := json.Unmarshal([]byte(*result.Messages[0].Body), &command)

	if err != nil {
		fmt.Println("json error: %s", err)
		return
	}

	if command.Action == "power" {
		if command.Value == "Standby" {
			tvOff()
		}
	}

	data, err := mustache.Render(commands[command.Action], command)

	if err != nil {
		fmt.Println("mustache error: %s", err)
		return
	}

	req, err := http.NewRequest("POST", fmt.Sprintf(`%v/YamahaRemoteControl/ctrl`, os.Getenv("RECEIVER_IP")), bytes.NewBuffer([]byte(data)))
  req.Header.Add("Content-Type", "text/xml")
	client := &http.Client{}
	resp, err := client.Do(req)

	if err != nil {
		fmt.Println("receiver error: %s", err)
	}
	resp.Body.Close()
}

func tvOff() {
	tv := samsung.TV{
		Host:            os.Getenv("TV_IP"),
		ApplicationID:   "go-samsung-tv",
		ApplicationName: "Ninja Sphere         ", // XXX: Currently needs padding
	}

	err := tv.SendCommand("KEY_POWEROFF")
	if err != nil {
		fmt.Println("tv error: %s", err)
	}

}
<commit_msg>updates<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sqs"
	"github.com/cbroglie/mustache"
	"github.com/ninjasphere/go-samsung-tv"
)

var commands = map[string]string{
	"power":       `<YAMAHA_AV cmd="PUT"><Main_Zone><Power_Control><Power>{{Value}}</Power></Power_Control></Main_Zone></YAMAHA_AV>`,
	"volume":      `<YAMAHA_AV cmd="PUT"><Main_Zone><Volume><Lvl><Val>{{Value}} {{Value2}} dB</Val><Exp></Exp><Unit></Unit></Lvl></Volume></Main_Zone></YAMAHA_AV>`, // <Val>Up 1 dB</Val>
	"volumeLevel": `<YAMAHA_AV cmd="PUT"><Main_Zone><Volume><Lvl><Val>{{Value}}</Val><Exp>1</Exp><Unit>dB</Unit></Lvl></Volume></Main_Zone></YAMAHA_AV>`,
	"mute":        `<YAMAHA_AV cmd="PUT"><Main_Zone><Volume><Mute>{{Value}}</Mute></Volume></Main_Zone></YAMAHA_AV>`,
	"input":       `<YAMAHA_AV cmd="PUT"><Main_Zone><Input><Input_Sel>{{Value}}</Input_Sel></Input></Main_Zone></YAMAHA_AV>`,
	"mode":        `<YAMAHA_AV cmd="PUT"><Main_Zone><Surround><Program_Sel><Current><Sound_Program>{{Value}}</Sound_Program></Current></Program_Sel></Surround></Main_Zone></YAMAHA_AV>`,
}

func main() {
	sess, err := session.NewSession(&aws.Config{
		Region:      aws.String(os.Getenv("AWS_REGION")),
		Credentials: credentials.NewSharedCredentials(os.Getenv("AWS_SHARED_CREDENTIALS_FILE"), os.Getenv("AWS_PROFILE")),
	})

	if err != nil {
		fmt.Println("session error: : %s", err)
		return
	}

	_, err = sess.Config.Credentials.Get()
	if err != nil {
		sess, err = session.NewSession(&aws.Config{
			Region: aws.String(os.Getenv("AWS_REGION")),
		})

		if err != nil {
			fmt.Println("session error: : %s", err)
			return
		}
	}

	_, err = sess.Config.Credentials.Get()
	if err != nil {
		fmt.Println(`unable to establish aws credentials: %v`, err.Error())
		return
	}

	svc := sqs.New(sess)
	qURL := os.Getenv("AWS_SQS_URL")

	for {
		sqsRequest(svc, qURL)
	}
}

func sqsRequest(svc *sqs.SQS, qURL string) {
	result, err := svc.ReceiveMessage(&sqs.ReceiveMessageInput{
		AttributeNames: []*string{
			aws.String(sqs.MessageSystemAttributeNameSentTimestamp),
		},
		MessageAttributeNames: []*string{
			aws.String(sqs.QueueAttributeNameAll),
		},
		QueueUrl:            &qURL,
		MaxNumberOfMessages: aws.Int64(1),
		VisibilityTimeout:   aws.Int64(0),
		WaitTimeSeconds:     aws.Int64(20),
	})

	if err != nil {
		fmt.Println("sqs error: %s", err)
		return
	}

	if len(result.Messages) == 0 {
		fmt.Println("Received no messages")
		return
	}

	sendReciever(result)
	removeFromQueue(svc, qURL, result)
}

func removeFromQueue(svc *sqs.SQS, qURL string, result *sqs.ReceiveMessageOutput) {
	_, err := svc.DeleteMessage(&sqs.DeleteMessageInput{
		QueueUrl:      &qURL,
		ReceiptHandle: result.Messages[0].ReceiptHandle,
	})

	if err != nil {
		fmt.Println("delete error: %s", err)
		return
	}
}

func sendReciever(result *sqs.ReceiveMessageOutput) {
	type message struct {
		Action string
		Value  string
		Value2 int
	}
	var command message

	err := json.Unmarshal([]byte(*result.Messages[0].Body), &command)

	if err != nil {
		fmt.Println("json error: %s", err)
		return
	}

	if command.Action == "power" {
		if command.Value == "Standby" {
			tvOff()
		}
	}

	data, err := mustache.Render(commands[command.Action], command)

	if err != nil {
		fmt.Println("mustache error: %s", err)
		return
	}

	req, err := http.NewRequest("POST", fmt.Sprintf(`%v/YamahaRemoteControl/ctrl`, os.Getenv("RECEIVER_IP")), bytes.NewBuffer([]byte(data)))
  req.Header.Add("Content-Type", "text/xml")
	client := &http.Client{}
	resp, err := client.Do(req)

	if err != nil {
		fmt.Println("receiver error: %s", err)
	}
	resp.Body.Close()
}

func tvOff() {
	tv := samsung.TV{
		Host:            os.Getenv("TV_IP"),
		ApplicationID:   "go-samsung-tv",
		ApplicationName: "Ninja Sphere         ", // XXX: Currently needs padding
	}

	err := tv.SendCommand("KEY_POWEROFF")
	if err != nil {
		fmt.Println("tv error: %s", err)
	}

}
<|endoftext|>


<commit_before>// Copyright (c) 2015, Stefano Brilli
// All rights reserved.
// This source code is released under the the 3 Clause BSD License.
// Read LICENSE at https://github.com/cybercase/gospace/blob/master/LICENSE

// Generates a script that set and updates environment variables recommended by
// https://golang.org/doc/code.html. See README file for example of use.

package main

import (
	"fmt"
	"os"
	"path"
	"path/filepath"
)

func main() {
	const outfile string = "activate"

	var wspace string
	if len(os.Args) == 2 {
		wspace = os.Args[1]
	}

	if wspace == "" {
		fmt.Fprintf(os.Stderr, "Usage: %s <workspace_dir>\n", filepath.Base(os.Args[0]))
		os.Exit(1)
	}

	if fi, err := os.Stat(wspace); os.IsNotExist(err) || !fi.IsDir() {
		fmt.Fprintf(os.Stderr, "Invalid Workspace: '%s'\n", wspace)
		os.Exit(1)
	}

	if path, err := filepath.Abs(wspace); err != nil {
		panic(err)
	} else {
		wspace = path
	}

	destFile, err := os.Create(path.Join(wspace, outfile))
	if err != nil {
		panic(err)
	}
	defer destFile.Close()  // TODO: consider deletion of this line

	_, err = fmt.Fprintf(destFile, template, wspace)
	if err != nil {
		panic(err)
	}
}

// Original: https://github.com/pypa/virtualenv/tree/develop/virtualenv_embedded
const template = `# This file must be used with "source bin/activate" *from bash*
# you cannot run it directly

deactivate () {
    # reset old environment variables
    if [ -n "$_OLD_WORKSPACE_PATH" ] ; then
        PATH="$_OLD_WORKSPACE_PATH"
        export PATH
        unset _OLD_WORKSPACE_PATH
    fi

    if [ -n "$_OLD_GOPATH" ] ; then
        GOPATH="$_OLD_GOPATH"
        export GOPATH
        unset _OLD_GOPATH
    fi

    # This should detect bash and zsh, which have a hash command that must
    # be called to get it to forget past commands.  Without forgetting
    # past commands the $PATH changes we made may not be respected
    if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then
        hash -r 2>/dev/null
    fi

    if [ -n "$_OLD_WORKSPACE_PS1" ] ; then
        PS1="$_OLD_WORKSPACE_PS1"
        export PS1
        unset _OLD_WORKSPACE_PS1
    fi

    unset WORKSPACE
    if [ ! "$1" = "nondestructive" ] ; then
    # Self destruct!
        unset -f deactivate
    fi
}

# unset irrelevant variables
deactivate nondestructive

WORKSPACE="%s"
export WORKSPACE

_OLD_GOPATH="$GOPATH"
GOPATH=$WORKSPACE
export GOPATH

_OLD_WORKSPACE_PATH="$PATH"
PATH="$WORKSPACE/bin:$PATH"
export PATH

if [ -z "$WORKSPACE_DISABLE_PROMPT" ] ; then
    _OLD_WORKSPACE_PS1="$PS1"
    if [ "x" != x ] ; then
        PS1="$PS1"
    else
        PS1="(` + "`" + `basename \"$WORKSPACE\"` + "`" + `)$PS1"
    fi
    export PS1
fi

# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands.  Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then
    hash -r 2>/dev/null
fi
`
<commit_msg>Remove parsing of first arg and template rendering<commit_after>// Copyright (c) 2015, Stefano Brilli
// All rights reserved.
// This source code is released under the the 3 Clause BSD License.
// Read LICENSE at https://github.com/cybercase/gospace/blob/master/LICENSE

// Generates a script that set and updates environment variables recommended by
// https://golang.org/doc/code.html. See README file for example of use.

package main

import (
	"fmt"
	"os"
	"path"
)

func main() {
	workingDir, err := os.Getwd()
	if err != nil {
		panic(err)
	}

	destFile, err := os.Create(path.Join(workingDir, "activate"))
	if err != nil {
		panic(err)
	}
	defer destFile.Close() // TODO: consider deletion of this line

	_, err = fmt.Fprintf(destFile, template)
	if err != nil {
		panic(err)
	}

	fmt.Println(`You're ready to go.

- Type 'source activate' jump into your new workspace
- Type 'deactivate' to step out`)
}

// Original: https://github.com/pypa/virtualenv/tree/develop/virtualenv_embedded
const template = `# This file must be used with "source bin/activate" *from bash*
# you cannot run it directly

deactivate () {
    # reset old environment variables
    if [ -n "$_OLD_WORKSPACE_PATH" ] ; then
        PATH="$_OLD_WORKSPACE_PATH"
        export PATH
        unset _OLD_WORKSPACE_PATH
    fi

    if [ -n "$_OLD_GOPATH" ] ; then
        GOPATH="$_OLD_GOPATH"
        export GOPATH
        unset _OLD_GOPATH
    fi

    # This should detect bash and zsh, which have a hash command that must
    # be called to get it to forget past commands.  Without forgetting
    # past commands the $PATH changes we made may not be respected
    if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then
        hash -r 2>/dev/null
    fi

    if [ -n "$_OLD_WORKSPACE_PS1" ] ; then
        PS1="$_OLD_WORKSPACE_PS1"
        export PS1
        unset _OLD_WORKSPACE_PS1
    fi

    unset WORKSPACE
    if [ ! "$1" = "nondestructive" ] ; then
    # Self destruct!
        unset -f deactivate
    fi
}

# unset irrelevant variables
deactivate nondestructive

WORKSPACE="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
export WORKSPACE

_OLD_GOPATH="$GOPATH"
GOPATH=$WORKSPACE
export GOPATH

_OLD_WORKSPACE_PATH="$PATH"
PATH="$WORKSPACE/bin:$PATH"
export PATH

if [ -z "$WORKSPACE_DISABLE_PROMPT" ] ; then
    _OLD_WORKSPACE_PS1="$PS1"
    if [ "x" != x ] ; then
        PS1="$PS1"
    else
        PS1="(` + "`" + `basename \"$WORKSPACE\"` + "`" + `)$PS1"
    fi
    export PS1
fi

# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands.  Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then
    hash -r 2>/dev/null
fi
`
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	goopt "github.com/droundy/goopt"
	// "github.com/mattbaird/elastigo"
	"encoding/json"
	"io/ioutil"
	"log"
	"net"
	"os"
	"strconv"
)

var port = goopt.Int([]string{"-p", "--port"}, 8125, "UDP Port to use")
var config = goopt.String([]string{"-c", "--config"}, "./config.json", "Configuration file to use")

func readConfig(filepath string) (map[string]interface{}, error) {
	file, e := ioutil.ReadFile(filepath)
	if e != nil {
		return nil, e
	}
	var cfg map[string]interface{}
	if e = json.Unmarshal(file, &cfg); e != nil {
		return nil, e
	}
	return cfg, nil
}

func getUDPAddressFromConfig(serverType string, cfg map[string]interface{}) (net.UDPAddr, error) {
	key := cfg[serverType].(map[string]interface{})
	parsedIp, _, e := net.ParseCIDR(key["ip"].(string))
	if e != nil {
		return net.UDPAddr{}, e
	}
	return net.UDPAddr{IP: parsedIp, Port: int(key["port"].(float64))}, e
}

func getTCPAddressFromConfig(serverType string, cfg map[string]interface{}) (net.TCPAddr, error) {
	key := cfg[serverType].(map[string]interface{})
	parsedIp, _, e := net.ParseCIDR(key["ip"].(string))
	if e != nil {
		return net.TCPAddr{}, e
	}
	return net.TCPAddr{IP: parsedIp, Port: int(key["port"].(float64))}, e
}

func sendToGraphite(message []byte, graphite net.UDPAddr) {

}

func main() {
	goopt.Description = func() string {
		return "Metric Wrapper for (at first) graphite & elasticsearch."
	}
	goopt.Version = "1.0"
	goopt.Summary = "gostats"
	goopt.Parse(nil)

	readConf, err := readConfig(*config)

	if err != nil {
		fmt.Printf("Failed to parse configuration file: %v\n", err)
		os.Exit(1)
	}

	cfg := readConf["config"].(map[string]interface{})

	elasticsearch, err := getTCPAddressFromConfig("elasticsearch", cfg)
	if err != nil {
		fmt.Printf("Failed to get Server address for Elasticsearch: %v\n", err)
		os.Exit(0)
	}

	graphite, err := getUDPAddressFromConfig("graphite", cfg)
	if err != nil {
		fmt.Printf("Failed to get Server address for Graphite: %v\n", err)
		os.Exit(0)
	}

	fmt.Println(elasticsearch)
	fmt.Println(graphite)

	addr, _ := net.ResolveUDPAddr("udp", ":"+strconv.Itoa(*port))
	fmt.Println(addr)
	conn, err := net.ListenUDP("udp", addr)

	if err != nil {
		fmt.Println(err.Error())
		return
	}
	defer conn.Close()

	for {
		message := make([]byte, 512)
		n, _, err := conn.ReadFromUDP(message)
		log.Printf("Got %d bytes\n", n)
		log.Printf("Data: %s", message)
		if err != nil || n == 0 {
			log.Printf("Error is: %s, bytes are: %d", err, n)
			continue
		}
		sendToGraphite(message, graphite)
	}
}
<commit_msg>implement sending to graphite<commit_after>package main

import (
	"fmt"
	goopt "github.com/droundy/goopt"
	// "github.com/mattbaird/elastigo"
	"encoding/json"
	"io/ioutil"
	"log"
	"net"
	"os"
	"strconv"
)

var port = goopt.Int([]string{"-p", "--port"}, 8125, "UDP Port to use")
var config = goopt.String([]string{"-c", "--config"}, "./config.json", "Configuration file to use")

func readConfig(filepath string) (map[string]interface{}, error) {
	file, e := ioutil.ReadFile(filepath)
	if e != nil {
		return nil, e
	}
	var cfg map[string]interface{}
	if e = json.Unmarshal(file, &cfg); e != nil {
		return nil, e
	}
	return cfg, nil
}

func getUDPAddressFromConfig(serverType string, cfg map[string]interface{}) (net.UDPAddr, error) {
	key := cfg[serverType].(map[string]interface{})
	parsedIp, _, e := net.ParseCIDR(key["ip"].(string))
	if e != nil {
		return net.UDPAddr{}, e
	}
	return net.UDPAddr{IP: parsedIp, Port: int(key["port"].(float64))}, e
}

func getTCPAddressFromConfig(serverType string, cfg map[string]interface{}) (net.TCPAddr, error) {
	key := cfg[serverType].(map[string]interface{})
	parsedIp, _, e := net.ParseCIDR(key["ip"].(string))
	if e != nil {
		return net.TCPAddr{}, e
	}
	return net.TCPAddr{IP: parsedIp, Port: int(key["port"].(float64))}, e
}

func sendToGraphite(message []byte, conn net.UDPConn, graphite net.UDPAddr) {
	if message != nil && len(message) > 0 {
		conn.WriteToUDP([]byte(fmt.Sprintf("%s|g", string(message))), &graphite)
	}
}

func main() {
	goopt.Description = func() string {
		return "Metric Wrapper for (at first) graphite & elasticsearch."
	}
	goopt.Version = "1.0"
	goopt.Summary = "gostats"
	goopt.Parse(nil)

	readConf, err := readConfig(*config)

	if err != nil {
		fmt.Printf("Failed to parse configuration file: %v\n", err)
		os.Exit(1)
	}

	cfg := readConf["config"].(map[string]interface{})

	elasticsearch, err := getTCPAddressFromConfig("elasticsearch", cfg)
	if err != nil {
		fmt.Printf("Failed to get Server address for Elasticsearch: %v\n", err)
		os.Exit(0)
	}

	graphite, err := getUDPAddressFromConfig("graphite", cfg)
	if err != nil {
		fmt.Printf("Failed to get Server address for Graphite: %v\n", err)
		os.Exit(0)
	}

	graphiteConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
	if err != nil {
		fmt.Printf("Failed to create UDP Connection: %v\n", err)
		os.Exit(0)
	}

	fmt.Println(elasticsearch)
	fmt.Println(graphite)

	addr, _ := net.ResolveUDPAddr("udp", ":"+strconv.Itoa(*port))
	fmt.Println(addr)
	conn, err := net.ListenUDP("udp", addr)

	if err != nil {
		fmt.Println(err.Error())
		return
	}
	defer conn.Close()

	for {
		message := make([]byte, 512)
		n, _, err := conn.ReadFromUDP(message)
		log.Printf("Got %d bytes\n", n)
		log.Printf("Data: %s", message)
		if err != nil || n == 0 {
			log.Printf("Error is: %s, bytes are: %d", err, n)
			continue
		}
		sendToGraphite(message, *graphiteConn, graphite)
	}
}
<|endoftext|>


<commit_before>package gotetra

import (
	"log"
	"path"
	"runtime"

	"github.com/phil-mansfield/gotetra/density"
	"github.com/phil-mansfield/gotetra/geom"
	"github.com/phil-mansfield/gotetra/io"
	"github.com/phil-mansfield/gotetra/rand"
)

const (
	UnitBufCount = 1 << 12
	tetraIntr = true
)

type Manager struct {
	// The currently loaded seet segment.
	xs, scaledXs []geom.Vec
	xCb geom.CellBounds
	hd io.SheetHeader

	renderers []renderer
	skip int
	unitBufs [][]geom.Vec

	// io related things
	log bool
	files []string
	ms runtime.MemStats

	// workspaces
	workers int
	workspaces []workspace
}

type renderer struct {
	box Box
	cb geom.CellBounds
	over Overlap
	validSegs map[string]bool
}

type workspace struct {
	buf []float64
	intr density.Interpolator
	lowX, highX int
}

func NewManager(files []string, boxes []Box, logFlag bool) (*Manager, error) {
	man := new(Manager)
	man.log = logFlag

	gen := rand.NewTimeSeed(rand.Tausworthe)
	man.unitBufs = make([][]geom.Vec, UnitBufCount)

	maxPoints := 0
	for _, b := range boxes {
		if b.Points() > maxPoints { maxPoints = b.Points() }
	}

	for bi := range man.unitBufs {
		man.unitBufs[bi] = make([]geom.Vec, maxPoints)
		buf := man.unitBufs[bi]
		for j := range buf {
			for k := 0; k < 3; k++ {
				buf[j][k] = float32(gen.Uniform(0, 1))
			}
		}
		geom.DistributeUnit(buf)
	}

	man.skip = 1

	man.workers = runtime.NumCPU()
	runtime.GOMAXPROCS(man.workers)
	man.workspaces = make([]workspace, man.workers)

	man.renderers = make([]renderer, len(boxes))
	for i := range man.renderers {
		man.renderers[i].box = boxes[i]
		man.renderers[i].cb = geom.CellBounds{
			boxes[i].CellOrigin(), boxes[i].CellSpan(),
		}
		man.renderers[i].validSegs = make(map[string]bool)
	}

	man.files = make([]string, 0)
	maxBufSize := 0

	for _, file := range files {
		err := io.ReadSheetHeaderAt(file, &man.hd)
		if err != nil { return nil, err }

		intersect := false
		for i := range boxes {
			cells := boxes[i].Cells()
			hCb := man.hd.CellBounds(cells)

			if hCb.Intersect(&man.renderers[i].cb, cells) {
				bufSize := boxes[i].Overlap(&man.hd).BufferSize()
				if bufSize > maxBufSize { maxBufSize = bufSize }

				man.renderers[i].validSegs[file] = true
				intersect = true
			}
		}

		if intersect {
			man.files = append(man.files, file)
		}
	}

	err := io.ReadSheetHeaderAt(files[0], &man.hd)
	if err != nil { return nil, err }
	man.xs = make([]geom.Vec, man.hd.GridCount)
	man.scaledXs = make([]geom.Vec, man.hd.GridCount)

	if man.log {
		log.Printf(
			"Workspace buffer size: %d. Number of workers: %d",
			maxBufSize, man.workers,
		)
	}

	for i := range man.workspaces {
		man.workspaces[i].buf = make([]float64, maxBufSize)
	}

	if man.log {
		runtime.ReadMemStats(&man.ms)
		log.Printf(
			"Alloc: %5d MB, Sys: %5d MB",
			man.ms.Alloc >> 20, man.ms.Sys >> 20,
		)
	}

	return man, nil
}

func (r *renderer) requiresFile(file string) bool {
	return r.validSegs[file]
}

func (r *renderer) scaleXs(man *Manager) {
	copy(man.scaledXs, man.xs)
	r.over.ScaleVecs(man.scaledXs)
}

func (r *renderer) ptVal(man *Manager) float64 {
	frac := float64(r.box.Cells()) / float64(man.hd.CountWidth)
	return frac * frac * frac
}

func (r *renderer) initWorkspaces(man *Manager) {
	segFrac := int(man.hd.SegmentWidth) / man.skip
	segLen := segFrac * segFrac * segFrac

	for i := range man.unitBufs {
		man.unitBufs[i] = man.unitBufs[i][0: r.box.Points()]
	}

	// TODO: fix this int64 silliness
	for id := range man.workspaces {
		man.workspaces[id].intr = density.MonteCarlo(
			man.hd.SegmentWidth, r.box.Points(), r.box.Cells(),
			int64(man.skip), man.unitBufs, r.over,
		)

		man.workspaces[id].buf =
			man.workspaces[id].buf[0: r.over.BufferSize()]
		man.workspaces[id].lowX = id * man.skip
		man.workspaces[id].highX = segLen
	}
}

func (man *Manager) Log(flag bool) { man.log = flag }

func (man *Manager) Subsample(subsampleLength int) {
	if !isPowTwo(man.skip) {
		log.Fatalf("Skip ingrement is %d, must be power of two.", man.skip)
	}

	man.skip = subsampleLength
}

func isPowTwo(x int) bool {
	for x & 1 == 0 && x > 0 { x >>= 1 }
	return x == 1
}

func (man *Manager) RenderDensity() error {
	for _, file := range man.files {
		err := man.RenderDensityFromFile(file)
		if err != nil { return err }
	}
	return nil
}

func (man *Manager) RenderDensityFromFile(file string) error {
	if man.log {
		log.Printf("Rendering file %s", path.Base(file))
	}

	err := man.loadFile(file)
	if err != nil { return err }

	out := make(chan int, man.workers)

	for ri := range man.renderers {
		r := &man.renderers[ri]

		if !r.requiresFile(file) { continue }
		man.xCb = *man.hd.CellBounds(r.box.Cells())

		r.over = r.box.Overlap(&man.hd)
		r.cb = geom.CellBounds{ r.box.CellOrigin(), r.box.CellSpan() }
		r.scaleXs(man)
		r.initWorkspaces(man)

		for id := 0; id < man.workers - 1; id++ {
			go man.chanInterpolate(id, r, out)
		}
		id := man.workers - 1
		man.chanInterpolate(id, r, out)

		for i := 0; i < man.workers; i++ {
			id := <-out
			r.over.Add(man.workspaces[id].buf, r.box.Vals())
		}
	}

	if man.log {
		runtime.ReadMemStats(&man.ms)
		log.Printf(
			"Alloc: %5d MB, Sys: %5d MB",
			man.ms.Alloc >> 20, man.ms.Sys >> 20,
		)
	}
	return nil
}

func (man *Manager) loadFile(file string) error {
	err := io.ReadSheetHeaderAt(file, &man.hd)
	if err != nil { return err }
	err = io.ReadSheetPositionsAt(file, man.xs)
	if err != nil { return err }
	runtime.GC()

	return nil
}

func (man *Manager) chanInterpolate(id int, r *renderer, out chan<- int) {
	w := &man.workspaces[id]

	for i := range w.buf { w.buf[i] = 0.0 }
	if tetraIntr {
		w.intr.Interpolate(
			w.buf, man.scaledXs,
			r.ptVal(man), w.lowX, w.highX,
			man.workers * man.skip,
		)
	} else {
		r.over.Interpolate(
			w.buf, man.scaledXs,
			r.ptVal(man), w.lowX, w.highX,
			man.workers * man.skip,
		)
	}

	out <- id
}
<commit_msg>Fixed bug which confused skip with jump.<commit_after>package gotetra

import (
	"log"
	"path"
	"runtime"

	"github.com/phil-mansfield/gotetra/density"
	"github.com/phil-mansfield/gotetra/geom"
	"github.com/phil-mansfield/gotetra/io"
	"github.com/phil-mansfield/gotetra/rand"
)

const (
	UnitBufCount = 1 << 12
	tetraIntr = true
)

type Manager struct {
	// The currently loaded seet segment.
	xs, scaledXs []geom.Vec
	xCb geom.CellBounds
	hd io.SheetHeader

	renderers []renderer
	skip int
	unitBufs [][]geom.Vec

	// io related things
	log bool
	files []string
	ms runtime.MemStats

	// workspaces
	workers int
	workspaces []workspace
}

type renderer struct {
	box Box
	cb geom.CellBounds
	over Overlap
	validSegs map[string]bool
}

type workspace struct {
	buf []float64
	intr density.Interpolator
	lowX, highX int
}

func NewManager(files []string, boxes []Box, logFlag bool) (*Manager, error) {
	man := new(Manager)
	man.log = logFlag

	gen := rand.NewTimeSeed(rand.Tausworthe)
	man.unitBufs = make([][]geom.Vec, UnitBufCount)

	maxPoints := 0
	for _, b := range boxes {
		if b.Points() > maxPoints { maxPoints = b.Points() }
	}

	for bi := range man.unitBufs {
		man.unitBufs[bi] = make([]geom.Vec, maxPoints)
		buf := man.unitBufs[bi]
		for j := range buf {
			for k := 0; k < 3; k++ {
				buf[j][k] = float32(gen.Uniform(0, 1))
			}
		}
		geom.DistributeUnit(buf)
	}

	man.skip = 1

	man.workers = runtime.NumCPU()
	runtime.GOMAXPROCS(man.workers)
	man.workspaces = make([]workspace, man.workers)

	man.renderers = make([]renderer, len(boxes))
	for i := range man.renderers {
		man.renderers[i].box = boxes[i]
		man.renderers[i].cb = geom.CellBounds{
			boxes[i].CellOrigin(), boxes[i].CellSpan(),
		}
		man.renderers[i].validSegs = make(map[string]bool)
	}

	man.files = make([]string, 0)
	maxBufSize := 0

	for _, file := range files {
		err := io.ReadSheetHeaderAt(file, &man.hd)
		if err != nil { return nil, err }

		intersect := false
		for i := range boxes {
			cells := boxes[i].Cells()
			hCb := man.hd.CellBounds(cells)

			if hCb.Intersect(&man.renderers[i].cb, cells) {
				bufSize := boxes[i].Overlap(&man.hd).BufferSize()
				if bufSize > maxBufSize { maxBufSize = bufSize }

				man.renderers[i].validSegs[file] = true
				intersect = true
			}
		}

		if intersect {
			man.files = append(man.files, file)
		}
	}

	err := io.ReadSheetHeaderAt(files[0], &man.hd)
	if err != nil { return nil, err }
	man.xs = make([]geom.Vec, man.hd.GridCount)
	man.scaledXs = make([]geom.Vec, man.hd.GridCount)

	if man.log {
		log.Printf(
			"Workspace buffer size: %d. Number of workers: %d",
			maxBufSize, man.workers,
		)
	}

	for i := range man.workspaces {
		man.workspaces[i].buf = make([]float64, maxBufSize)
	}

	if man.log {
		runtime.ReadMemStats(&man.ms)
		log.Printf(
			"Alloc: %5d MB, Sys: %5d MB",
			man.ms.Alloc >> 20, man.ms.Sys >> 20,
		)
	}

	return man, nil
}

func (r *renderer) requiresFile(file string) bool {
	return r.validSegs[file]
}

func (r *renderer) scaleXs(man *Manager) {
	copy(man.scaledXs, man.xs)
	r.over.ScaleVecs(man.scaledXs)
}

func (r *renderer) ptVal(man *Manager) float64 {
	frac := float64(r.box.Cells()) / float64(man.hd.CountWidth)
	return frac * frac * frac
}

func (r *renderer) initWorkspaces(man *Manager) {
	segFrac := int(man.hd.SegmentWidth) / man.skip
	segLen := segFrac * segFrac * segFrac

	for i := range man.unitBufs {
		man.unitBufs[i] = man.unitBufs[i][0: r.box.Points()]
	}

	// TODO: fix this int64 silliness
	for id := range man.workspaces {
		man.workspaces[id].intr = density.MonteCarlo(
			man.hd.SegmentWidth, r.box.Points(), r.box.Cells(),
			int64(man.skip), man.unitBufs, r.over,
		)

		man.workspaces[id].buf =
			man.workspaces[id].buf[0: r.over.BufferSize()]
		man.workspaces[id].lowX = id * man.skip
		man.workspaces[id].highX = segLen
	}
}

func (man *Manager) Log(flag bool) { man.log = flag }

func (man *Manager) Subsample(subsampleLength int) {
	if !isPowTwo(man.skip) {
		log.Fatalf("Skip ingrement is %d, must be power of two.", man.skip)
	}

	man.skip = subsampleLength
}

func isPowTwo(x int) bool {
	for x & 1 == 0 && x > 0 { x >>= 1 }
	return x == 1
}

func (man *Manager) RenderDensity() error {
	for _, file := range man.files {
		err := man.RenderDensityFromFile(file)
		if err != nil { return err }
	}
	return nil
}

func (man *Manager) RenderDensityFromFile(file string) error {
	if man.log {
		log.Printf("Rendering file %s", path.Base(file))
	}

	err := man.loadFile(file)
	if err != nil { return err }

	out := make(chan int, man.workers)

	for ri := range man.renderers {
		r := &man.renderers[ri]

		if !r.requiresFile(file) { continue }
		man.xCb = *man.hd.CellBounds(r.box.Cells())

		r.over = r.box.Overlap(&man.hd)
		r.cb = geom.CellBounds{ r.box.CellOrigin(), r.box.CellSpan() }
		r.scaleXs(man)
		r.initWorkspaces(man)

		for id := 0; id < man.workers - 1; id++ {
			go man.chanInterpolate(id, r, out)
		}
		id := man.workers - 1
		man.chanInterpolate(id, r, out)

		for i := 0; i < man.workers; i++ {
			id := <-out
			r.over.Add(man.workspaces[id].buf, r.box.Vals())
		}
	}

	if man.log {
		runtime.ReadMemStats(&man.ms)
		log.Printf(
			"Alloc: %5d MB, Sys: %5d MB",
			man.ms.Alloc >> 20, man.ms.Sys >> 20,
		)
	}
	return nil
}

func (man *Manager) loadFile(file string) error {
	err := io.ReadSheetHeaderAt(file, &man.hd)
	if err != nil { return err }
	err = io.ReadSheetPositionsAt(file, man.xs)
	if err != nil { return err }
	runtime.GC()

	return nil
}

func (man *Manager) chanInterpolate(id int, r *renderer, out chan<- int) {
	w := &man.workspaces[id]

	for i := range w.buf { w.buf[i] = 0.0 }
	if tetraIntr {
		w.intr.Interpolate(
			w.buf, man.scaledXs,
			r.ptVal(man), w.lowX, w.highX,
			man.workers,
		)
	} else {
		r.over.Interpolate(
			w.buf, man.scaledXs,
			r.ptVal(man), w.lowX, w.highX,
			man.workers,
		)
	}

	out <- id
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	_ "github.com/davecgh/go-spew/spew"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"sync/atomic"
	"syscall"
	"unsafe"
)

type UrlMap map[string]string

// we use COW to handle reloading the map on SIGHUP; this avoids having to
// use an expensive mutex in the HTTP handler
var urlmap unsafe.Pointer
var defaultUrl string
var sigChan = make(chan os.Signal, 1)

// loads the URL mappings from urls.txt
func loadMap() {
	file, err := os.Open("urls.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()
	urls := make(UrlMap)
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if strings.HasPrefix(line, "#") || len(line) < 1 {
			continue
		}
		// TODO: warn if duplicate key!
		parts := strings.Split(line, " ")
		if len(parts) == 2 {
			urls[strings.ToLower(strings.TrimSpace(parts[0]))] = strings.TrimSpace(parts[1])
		} else {
			log.Printf("skipping malformed line:\n%s", line)
		}
	}
	if err := scanner.Err(); err != nil {
		log.Println(err)
	}
	log.Printf("loaded %d mappings", len(urls))
	// spew.Dump(urls)
	atomic.StorePointer(&urlmap, (unsafe.Pointer)(&urls))
}

// HTTP handler. If the requested URL is in our map,
// we redirect to the target. Otherwise we redirect to
// the default URL if specified.
func handler(w http.ResponseWriter, r *http.Request) {
	log.Println(r.URL.Path)
	urls := *(*UrlMap)(atomic.LoadPointer(&urlmap))
	dest, ok := urls[strings.ToLower(r.URL.Path)]
	if !ok {
		if defaultUrl != "" {
			http.Redirect(w, r, defaultUrl, 302)
			return
		} else {
			http.Error(w, "not found", 404)
			return
		}
	}
	http.Redirect(w, r, dest, 302)
}

func main() {
	loadMap()
	signal.Notify(sigChan, syscall.SIGHUP)
	go func() {
		for _ = range sigChan {
			log.Println("got SIGHUP; reloading urls.txt")
			loadMap()
		}
	}()

	var pPort = flag.Int("port", 80, "listening port")
	var pUrl = flag.String("defaultUrl", "http://google.com", "default URL")
	flag.Parse()

	defaultUrl = *pUrl
	http.HandleFunc("/", handler)
	log.Printf("pid %d; listening on port %d", os.Getpid(), *pPort)
	err := http.ListenAndServe(fmt.Sprintf(":%d", *pPort), nil)
	if err != nil {
		log.Fatal(err)
	}
}
<commit_msg>warn about duplicate keys; improve logging<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"sync/atomic"
	"syscall"
	"unsafe"
)

type UrlMap map[string]string

// we use COW to handle reloading the map on SIGHUP; this avoids having to
// use an expensive mutex in the HTTP handler
var urlmap unsafe.Pointer
var defaultUrl string
var sigChan = make(chan os.Signal, 1)

// loads the URL mappings from urls.txt
func loadMap() {
	file, err := os.Open("urls.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()
	urls := make(UrlMap)
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if strings.HasPrefix(line, "#") || len(line) < 1 {
			continue
		}
		parts := strings.Split(line, " ")
		if len(parts) == 2 {
			key := strings.ToLower(strings.TrimSpace(parts[0]))
			if _, ok := urls[key]; ok {
				log.Printf("duplicate key %s!", key)
			}
			urls[key] = strings.TrimSpace(parts[1])
		} else {
			log.Printf("skipping malformed line:\n%s", line)
		}
	}
	if err := scanner.Err(); err != nil {
		log.Println(err)
	}
	log.Printf("loaded %d mappings", len(urls))
	atomic.StorePointer(&urlmap, (unsafe.Pointer)(&urls))
}

// HTTP handler. If the requested URL is in our map,
// we redirect to the target. Otherwise we redirect to
// the default URL if specified.
func handler(w http.ResponseWriter, r *http.Request) {
	urls := *(*UrlMap)(atomic.LoadPointer(&urlmap))
	dest, ok := urls[strings.ToLower(r.URL.Path)]
	if !ok {
		log.Printf("no match for %s", r.URL.Path)
		if defaultUrl != "" {
			http.Redirect(w, r, defaultUrl, 302)
			return
		} else {
			http.Error(w, "not found", 404)
			return
		}
	}
	log.Printf("%s -> %s", r.URL.Path, dest)
	http.Redirect(w, r, dest, 302)
}

func main() {
	loadMap()
	signal.Notify(sigChan, syscall.SIGHUP)
	go func() {
		for _ = range sigChan {
			log.Println("got SIGHUP; reloading urls.txt")
			loadMap()
		}
	}()

	var pPort = flag.Int("port", 80, "listening port")
	var pUrl = flag.String("defaultUrl", "http://google.com", "default URL")
	flag.Parse()

	defaultUrl = *pUrl
	http.HandleFunc("/", handler)
	log.Printf("pid %d; listening on port %d", os.Getpid(), *pPort)
	err := http.ListenAndServe(fmt.Sprintf(":%d", *pPort), nil)
	if err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>