<commit_before>/*
Copyright 2014 Google Inc. All rights reserved.

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 registry

import (
	"encoding/json"
	"net/url"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
)

// Implementation of RESTStorage for the api server.
type ControllerRegistryStorage struct {
	registry ControllerRegistry
}

func MakeControllerRegistryStorage(registry ControllerRegistry) apiserver.RESTStorage {
	return &ControllerRegistryStorage{
		registry: registry,
	}
}

func (storage *ControllerRegistryStorage) List(*url.URL) (interface{}, error) {
	result := api.ReplicationControllerList{JSONBase: api.JSONBase{Kind: "cluster#replicationControllerList"}}
	controllers, err := storage.registry.ListControllers()
	if err == nil {
		result = api.ReplicationControllerList{
			JSONBase: api.JSONBase{Kind: "cluster#replicationControllerList"},
			Items:    controllers,
		}
	}
	return result, err
}

func (storage *ControllerRegistryStorage) Get(id string) (interface{}, error) {
	controller, err := storage.registry.GetController(id)
	if err != nil {
		return nil, err
	}
	controller.Kind = "cluster#replicationController"
	return controller, err
}

func (storage *ControllerRegistryStorage) Delete(id string) error {
	return storage.registry.DeleteController(id)
}

func (storage *ControllerRegistryStorage) Extract(body string) (interface{}, error) {
	result := api.ReplicationController{}
	err := json.Unmarshal([]byte(body), &result)
	result.Kind = "cluster#replicationController"
	return result, err
}

func (storage *ControllerRegistryStorage) Create(controller interface{}) error {
	return storage.registry.CreateController(controller.(api.ReplicationController))
}

func (storage *ControllerRegistryStorage) Update(controller interface{}) error {
	return storage.registry.UpdateController(controller.(api.ReplicationController))
}
<commit_msg>Implement label queries for controller registry<commit_after>/*
Copyright 2014 Google Inc. All rights reserved.

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 registry

import (
	"encoding/json"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
)

// Implementation of RESTStorage for the api server.
type ControllerRegistryStorage struct {
	registry ControllerRegistry
}

func MakeControllerRegistryStorage(registry ControllerRegistry) apiserver.RESTStorage {
	return &ControllerRegistryStorage{
		registry: registry,
	}
}

func (storage *ControllerRegistryStorage) List(query labels.Query) (interface{}, error) {
	result := api.ReplicationControllerList{JSONBase: api.JSONBase{Kind: "cluster#replicationControllerList"}}
	controllers, err := storage.registry.ListControllers()
	if err == nil {
		for _, controller := range controllers {
			if query.Matches(labels.LabelSet(controller.Labels)) {
				result.Items = append(result.Items, controller)
			}
		}
	}
	return result, err
}

func (storage *ControllerRegistryStorage) Get(id string) (interface{}, error) {
	controller, err := storage.registry.GetController(id)
	if err != nil {
		return nil, err
	}
	controller.Kind = "cluster#replicationController"
	return controller, err
}

func (storage *ControllerRegistryStorage) Delete(id string) error {
	return storage.registry.DeleteController(id)
}

func (storage *ControllerRegistryStorage) Extract(body string) (interface{}, error) {
	result := api.ReplicationController{}
	err := json.Unmarshal([]byte(body), &result)
	result.Kind = "cluster#replicationController"
	return result, err
}

func (storage *ControllerRegistryStorage) Create(controller interface{}) error {
	return storage.registry.CreateController(controller.(api.ReplicationController))
}

func (storage *ControllerRegistryStorage) Update(controller interface{}) error {
	return storage.registry.UpdateController(controller.(api.ReplicationController))
}
<|endoftext|>


<commit_before>package stringutils

import "testing"

func TestRandomString(t *testing.T) {
	str := GenerateRandomString()
	if len(str) != 64 {
		t.Fatalf("Id returned is incorrect: %s", str)
	}
}

func TestRandomStringUniqueness(t *testing.T) {
	repeats := 25
	set := make(map[string]struct{}, repeats)
	for i := 0; i < repeats; i = i + 1 {
		str := GenerateRandomString()
		if len(str) != 64 {
			t.Fatalf("Id returned is incorrect: %s", str)
		}
		if _, ok := set[str]; ok {
			t.Fatalf("Random number is repeated")
		}
		set[str] = struct{}{}
	}
}
<commit_msg>Added unit tests for stringutils GenerateRandomAlphaOnlyString and GenerateRandomAsciiString<commit_after>package stringutils

import "testing"

func TestRandomString(t *testing.T) {
	str := GenerateRandomString()
	if len(str) != 64 {
		t.Fatalf("Id returned is incorrect: %s", str)
	}
}

func TestRandomStringUniqueness(t *testing.T) {
	repeats := 25
	set := make(map[string]struct{}, repeats)
	for i := 0; i < repeats; i = i + 1 {
		str := GenerateRandomString()
		if len(str) != 64 {
			t.Fatalf("Id returned is incorrect: %s", str)
		}
		if _, ok := set[str]; ok {
			t.Fatalf("Random number is repeated")
		}
		set[str] = struct{}{}
	}
}

func testLengthHelper(generator func(int) string, t *testing.T) {
	expectedLength := 20
	s := generator(expectedLength)
	if len(s) != expectedLength {
		t.Fatalf("Length of %s was %d but expected length %d", s, len(s), expectedLength)
	}
}

func testUniquenessHelper(generator func(int) string, t *testing.T) {
	repeats := 25
	set := make(map[string]struct{}, repeats)
	for i := 0; i < repeats; i = i + 1 {
		str := generator(64)
		if len(str) != 64 {
			t.Fatalf("Id returned is incorrect: %s", str)
		}
		if _, ok := set[str]; ok {
			t.Fatalf("Random number is repeated")
		}
		set[str] = struct{}{}
	}
}

func isASCII(s string) bool {
	for _, c := range s {
		if c > 127 {
			return false
		}
	}
	return true
}

func TestGenerateRandomAlphaOnlyStringLength(t *testing.T) {
	testLengthHelper(GenerateRandomAlphaOnlyString, t)
}

func TestGenerateRandomAlphaOnlyStringUniqueness(t *testing.T) {
	testUniquenessHelper(GenerateRandomAlphaOnlyString, t)
}

func TestGenerateRandomAsciiStringLength(t *testing.T) {
	testLengthHelper(GenerateRandomAsciiString, t)
}

func TestGenerateRandomAsciiStringUniqueness(t *testing.T) {
	testUniquenessHelper(GenerateRandomAsciiString, t)
}

func TestGenerateRandomAsciiStringIsAscii(t *testing.T) {
	str := GenerateRandomAsciiString(64)
	if !isASCII(str) {
		t.Fatalf("%s contained non-ascii characters", str)
	}
}
<|endoftext|>


<commit_before>package stackdriver

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"math"
	"net/http"
	"net/url"
	"path"
	"regexp"
	"strconv"
	"strings"
	"time"

	"golang.org/x/net/context/ctxhttp"

	"github.com/grafana/grafana/pkg/api/pluginproxy"
	"github.com/grafana/grafana/pkg/components/null"
	"github.com/grafana/grafana/pkg/components/simplejson"
	"github.com/grafana/grafana/pkg/log"
	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/plugins"
	"github.com/grafana/grafana/pkg/setting"
	"github.com/grafana/grafana/pkg/tsdb"
	"github.com/opentracing/opentracing-go"
)

var slog log.Logger

// StackdriverExecutor executes queries for the Stackdriver datasource
type StackdriverExecutor struct {
	httpClient *http.Client
	dsInfo     *models.DataSource
}

// NewStackdriverExecutor initializes a http client
func NewStackdriverExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
	httpClient, err := dsInfo.GetHttpClient()
	if err != nil {
		return nil, err
	}

	return &StackdriverExecutor{
		httpClient: httpClient,
		dsInfo:     dsInfo,
	}, nil
}

func init() {
	slog = log.New("tsdb.stackdriver")
	tsdb.RegisterTsdbQueryEndpoint("stackdriver", NewStackdriverExecutor)
}

// Query takes in the frontend queries, parses them into the Stackdriver query format
// executes the queries against the Stackdriver API and parses the response into
// the time series or table format
func (e *StackdriverExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
	result := &tsdb.Response{
		Results: make(map[string]*tsdb.QueryResult),
	}

	queries, err := e.buildQueries(tsdbQuery)
	if err != nil {
		return nil, err
	}

	for _, query := range queries {
		queryRes, err := e.executeQuery(ctx, query, tsdbQuery)
		if err != nil {
			return nil, err
		}
		result.Results[query.RefID] = queryRes
	}

	return result, nil
}

func (e *StackdriverExecutor) buildQueries(tsdbQuery *tsdb.TsdbQuery) ([]*StackdriverQuery, error) {
	stackdriverQueries := []*StackdriverQuery{}

	startTime, err := tsdbQuery.TimeRange.ParseFrom()
	if err != nil {
		return nil, err
	}

	endTime, err := tsdbQuery.TimeRange.ParseTo()
	if err != nil {
		return nil, err
	}

	for _, query := range tsdbQuery.Queries {
		var target string

		if fullTarget, err := query.Model.Get("targetFull").String(); err == nil {
			target = fixIntervalFormat(fullTarget)
		} else {
			target = fixIntervalFormat(query.Model.Get("target").MustString())
		}

		metricType := query.Model.Get("metricType").MustString()
		filterParts := query.Model.Get("filters").MustArray()

		filterString := ""
		for i, part := range filterParts {
			mod := i % 4
			if part == "AND" {
				filterString += " "
			} else if mod == 2 {
				filterString += fmt.Sprintf(`"%s"`, part)
			} else {
				filterString += part.(string)
			}
		}

		params := url.Values{}
		params.Add("interval.startTime", startTime.UTC().Format(time.RFC3339))
		params.Add("interval.endTime", endTime.UTC().Format(time.RFC3339))
		params.Add("filter", strings.Trim(fmt.Sprintf(`metric.type="%s" %s`, metricType, filterString), " "))
		params.Add("view", query.Model.Get("view").MustString())
		setAggParams(&params, query)

		if setting.Env == setting.DEV {
			slog.Debug("Stackdriver request", "params", params)
		}

		groupBys := query.Model.Get("groupBys").MustArray()
		groupBysAsStrings := make([]string, 0)
		for _, groupBy := range groupBys {
			groupBysAsStrings = append(groupBysAsStrings, groupBy.(string))
		}

		stackdriverQueries = append(stackdriverQueries, &StackdriverQuery{
			Target:   target,
			Params:   params,
			RefID:    query.RefId,
			GroupBys: groupBysAsStrings,
		})
	}

	return stackdriverQueries, nil
}

func setAggParams(params *url.Values, query *tsdb.Query) {
	primaryAggregation := query.Model.Get("primaryAggregation").MustString()
	perSeriesAligner := query.Model.Get("perSeriesAligner").MustString()
	alignmentPeriod := query.Model.Get("alignmentPeriod").MustString()

	if primaryAggregation == "" {
		primaryAggregation = "REDUCE_NONE"
	}

	if perSeriesAligner == "" {
		perSeriesAligner = "ALIGN_MEAN"
	}

	if alignmentPeriod == "auto" || alignmentPeriod == "" {
		alignmentPeriodValue := int(math.Max(float64(query.IntervalMs), 60.0))
		alignmentPeriod = "+" + strconv.Itoa(alignmentPeriodValue) + "s"
	}

	re := regexp.MustCompile("[0-9]+")
	aa, err := strconv.ParseInt(re.FindString(alignmentPeriod), 10, 64)
	if err != nil || aa > 3600 {
		alignmentPeriod = "+3600s"
	}

	params.Add("aggregation.crossSeriesReducer", primaryAggregation)
	params.Add("aggregation.perSeriesAligner", perSeriesAligner)
	params.Add("aggregation.alignmentPeriod", alignmentPeriod)

	groupBys := query.Model.Get("groupBys").MustArray()
	if len(groupBys) > 0 {
		for i := 0; i < len(groupBys); i++ {
			params.Add("aggregation.groupByFields", groupBys[i].(string))
		}
	}
}

func (e *StackdriverExecutor) executeQuery(ctx context.Context, query *StackdriverQuery, tsdbQuery *tsdb.TsdbQuery) (*tsdb.QueryResult, error) {
	queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: query.RefID}

	req, err := e.createRequest(ctx, e.dsInfo)
	if err != nil {
		queryResult.Error = err
		return queryResult, nil
	}

	req.URL.RawQuery = query.Params.Encode()
	queryResult.Meta.Set("rawQuery", req.URL.RawQuery)

	span, ctx := opentracing.StartSpanFromContext(ctx, "stackdriver query")
	span.SetTag("target", query.Target)
	span.SetTag("from", tsdbQuery.TimeRange.From)
	span.SetTag("until", tsdbQuery.TimeRange.To)
	span.SetTag("datasource_id", e.dsInfo.Id)
	span.SetTag("org_id", e.dsInfo.OrgId)

	defer span.Finish()

	opentracing.GlobalTracer().Inject(
		span.Context(),
		opentracing.HTTPHeaders,
		opentracing.HTTPHeadersCarrier(req.Header))

	res, err := ctxhttp.Do(ctx, e.httpClient, req)
	if err != nil {
		queryResult.Error = err
		return queryResult, nil
	}

	data, err := e.unmarshalResponse(res)
	if err != nil {
		queryResult.Error = err
		return queryResult, nil
	}

	err = e.parseResponse(queryResult, data, query)
	if err != nil {
		queryResult.Error = err
		return queryResult, nil
	}

	return queryResult, nil
}

func (e *StackdriverExecutor) unmarshalResponse(res *http.Response) (StackdriverResponse, error) {
	body, err := ioutil.ReadAll(res.Body)
	defer res.Body.Close()
	if err != nil {
		return StackdriverResponse{}, err
	}

	if res.StatusCode/100 != 2 {
		slog.Error("Request failed", "status", res.Status, "body", string(body))
		return StackdriverResponse{}, fmt.Errorf(string(body))
	}

	var data StackdriverResponse
	err = json.Unmarshal(body, &data)
	if err != nil {
		slog.Error("Failed to unmarshal Stackdriver response", "error", err, "status", res.Status, "body", string(body))
		return StackdriverResponse{}, err
	}

	return data, nil
}

func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data StackdriverResponse, query *StackdriverQuery) error {
	metricLabels := make(map[string][]string)
	resourceLabels := make(map[string][]string)

	for _, series := range data.TimeSeries {
		points := make([]tsdb.TimePoint, 0)

		// reverse the order to be ascending
		for i := len(series.Points) - 1; i >= 0; i-- {
			point := series.Points[i]
			points = append(points, tsdb.NewTimePoint(null.FloatFrom(point.Value.DoubleValue), float64((point.Interval.EndTime).Unix())*1000))
		}
		metricName := series.Metric.Type

		for key, value := range series.Metric.Labels {
			if !containsLabel(metricLabels[key], value) {
				metricLabels[key] = append(metricLabels[key], value)
			}
			if len(query.GroupBys) == 0 || containsLabel(query.GroupBys, "metric.label."+key) {
				metricName += " " + value
			}
		}

		for key, value := range series.Resource.Labels {
			if !containsLabel(resourceLabels[key], value) {
				resourceLabels[key] = append(resourceLabels[key], value)
			}

			if containsLabel(query.GroupBys, "resource.label."+key) {
				metricName += " " + value
			}
		}

		queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{
			Name:   metricName,
			Points: points,
		})
	}

	queryRes.Meta.Set("resourceLabels", resourceLabels)
	queryRes.Meta.Set("metricLabels", metricLabels)
	queryRes.Meta.Set("groupBys", query.GroupBys)

	return nil
}

func containsLabel(labels []string, newLabel string) bool {
	for _, val := range labels {
		if val == newLabel {
			return true
		}
	}
	return false
}

func (e *StackdriverExecutor) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) {
	u, _ := url.Parse(dsInfo.Url)
	u.Path = path.Join(u.Path, "render")

	req, err := http.NewRequest(http.MethodGet, "https://monitoring.googleapis.com/", nil)
	if err != nil {
		slog.Info("Failed to create request", "error", err)
		return nil, fmt.Errorf("Failed to create request. error: %v", err)
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("User-Agent", fmt.Sprintf("grafana-sd-plugin/%s", setting.BuildVersion))

	// find plugin
	plugin, ok := plugins.DataSources[dsInfo.Type]
	if !ok {
		return nil, errors.New("Unable to find datasource plugin Stackdriver")
	}
	projectName := dsInfo.JsonData.Get("defaultProject").MustString()
	proxyPass := fmt.Sprintf("stackdriver%s", "v3/projects/"+projectName+"/timeSeries")

	var stackdriverRoute *plugins.AppPluginRoute
	for _, route := range plugin.Routes {
		if route.Path == "stackdriver" {
			stackdriverRoute = route
			break
		}
	}

	pluginproxy.ApplyRoute(ctx, req, proxyPass, stackdriverRoute, dsInfo)

	return req, nil
}

func fixIntervalFormat(target string) string {
	rMinute := regexp.MustCompile(`'(\d+)m'`)
	rMin := regexp.MustCompile("m")
	target = rMinute.ReplaceAllStringFunc(target, func(m string) string {
		return rMin.ReplaceAllString(m, "min")
	})
	rMonth := regexp.MustCompile(`'(\d+)M'`)
	rMon := regexp.MustCompile("M")
	target = rMonth.ReplaceAllStringFunc(target, func(M string) string {
		return rMon.ReplaceAllString(M, "mon")
	})
	return target
}
<commit_msg>fix: use same User-Agent header as in other places in grafana when making external requests<commit_after>package stackdriver

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"math"
	"net/http"
	"net/url"
	"path"
	"regexp"
	"strconv"
	"strings"
	"time"

	"golang.org/x/net/context/ctxhttp"

	"github.com/grafana/grafana/pkg/api/pluginproxy"
	"github.com/grafana/grafana/pkg/components/null"
	"github.com/grafana/grafana/pkg/components/simplejson"
	"github.com/grafana/grafana/pkg/log"
	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/plugins"
	"github.com/grafana/grafana/pkg/setting"
	"github.com/grafana/grafana/pkg/tsdb"
	"github.com/opentracing/opentracing-go"
)

var slog log.Logger

// StackdriverExecutor executes queries for the Stackdriver datasource
type StackdriverExecutor struct {
	httpClient *http.Client
	dsInfo     *models.DataSource
}

// NewStackdriverExecutor initializes a http client
func NewStackdriverExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
	httpClient, err := dsInfo.GetHttpClient()
	if err != nil {
		return nil, err
	}

	return &StackdriverExecutor{
		httpClient: httpClient,
		dsInfo:     dsInfo,
	}, nil
}

func init() {
	slog = log.New("tsdb.stackdriver")
	tsdb.RegisterTsdbQueryEndpoint("stackdriver", NewStackdriverExecutor)
}

// Query takes in the frontend queries, parses them into the Stackdriver query format
// executes the queries against the Stackdriver API and parses the response into
// the time series or table format
func (e *StackdriverExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
	result := &tsdb.Response{
		Results: make(map[string]*tsdb.QueryResult),
	}

	queries, err := e.buildQueries(tsdbQuery)
	if err != nil {
		return nil, err
	}

	for _, query := range queries {
		queryRes, err := e.executeQuery(ctx, query, tsdbQuery)
		if err != nil {
			return nil, err
		}
		result.Results[query.RefID] = queryRes
	}

	return result, nil
}

func (e *StackdriverExecutor) buildQueries(tsdbQuery *tsdb.TsdbQuery) ([]*StackdriverQuery, error) {
	stackdriverQueries := []*StackdriverQuery{}

	startTime, err := tsdbQuery.TimeRange.ParseFrom()
	if err != nil {
		return nil, err
	}

	endTime, err := tsdbQuery.TimeRange.ParseTo()
	if err != nil {
		return nil, err
	}

	for _, query := range tsdbQuery.Queries {
		var target string

		if fullTarget, err := query.Model.Get("targetFull").String(); err == nil {
			target = fixIntervalFormat(fullTarget)
		} else {
			target = fixIntervalFormat(query.Model.Get("target").MustString())
		}

		metricType := query.Model.Get("metricType").MustString()
		filterParts := query.Model.Get("filters").MustArray()

		filterString := ""
		for i, part := range filterParts {
			mod := i % 4
			if part == "AND" {
				filterString += " "
			} else if mod == 2 {
				filterString += fmt.Sprintf(`"%s"`, part)
			} else {
				filterString += part.(string)
			}
		}

		params := url.Values{}
		params.Add("interval.startTime", startTime.UTC().Format(time.RFC3339))
		params.Add("interval.endTime", endTime.UTC().Format(time.RFC3339))
		params.Add("filter", strings.Trim(fmt.Sprintf(`metric.type="%s" %s`, metricType, filterString), " "))
		params.Add("view", query.Model.Get("view").MustString())
		setAggParams(&params, query)

		if setting.Env == setting.DEV {
			slog.Debug("Stackdriver request", "params", params)
		}

		groupBys := query.Model.Get("groupBys").MustArray()
		groupBysAsStrings := make([]string, 0)
		for _, groupBy := range groupBys {
			groupBysAsStrings = append(groupBysAsStrings, groupBy.(string))
		}

		stackdriverQueries = append(stackdriverQueries, &StackdriverQuery{
			Target:   target,
			Params:   params,
			RefID:    query.RefId,
			GroupBys: groupBysAsStrings,
		})
	}

	return stackdriverQueries, nil
}

func setAggParams(params *url.Values, query *tsdb.Query) {
	primaryAggregation := query.Model.Get("primaryAggregation").MustString()
	perSeriesAligner := query.Model.Get("perSeriesAligner").MustString()
	alignmentPeriod := query.Model.Get("alignmentPeriod").MustString()

	if primaryAggregation == "" {
		primaryAggregation = "REDUCE_NONE"
	}

	if perSeriesAligner == "" {
		perSeriesAligner = "ALIGN_MEAN"
	}

	if alignmentPeriod == "auto" || alignmentPeriod == "" {
		alignmentPeriodValue := int(math.Max(float64(query.IntervalMs), 60.0))
		alignmentPeriod = "+" + strconv.Itoa(alignmentPeriodValue) + "s"
	}

	re := regexp.MustCompile("[0-9]+")
	aa, err := strconv.ParseInt(re.FindString(alignmentPeriod), 10, 64)
	if err != nil || aa > 3600 {
		alignmentPeriod = "+3600s"
	}

	params.Add("aggregation.crossSeriesReducer", primaryAggregation)
	params.Add("aggregation.perSeriesAligner", perSeriesAligner)
	params.Add("aggregation.alignmentPeriod", alignmentPeriod)

	groupBys := query.Model.Get("groupBys").MustArray()
	if len(groupBys) > 0 {
		for i := 0; i < len(groupBys); i++ {
			params.Add("aggregation.groupByFields", groupBys[i].(string))
		}
	}
}

func (e *StackdriverExecutor) executeQuery(ctx context.Context, query *StackdriverQuery, tsdbQuery *tsdb.TsdbQuery) (*tsdb.QueryResult, error) {
	queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: query.RefID}

	req, err := e.createRequest(ctx, e.dsInfo)
	if err != nil {
		queryResult.Error = err
		return queryResult, nil
	}

	req.URL.RawQuery = query.Params.Encode()
	queryResult.Meta.Set("rawQuery", req.URL.RawQuery)

	span, ctx := opentracing.StartSpanFromContext(ctx, "stackdriver query")
	span.SetTag("target", query.Target)
	span.SetTag("from", tsdbQuery.TimeRange.From)
	span.SetTag("until", tsdbQuery.TimeRange.To)
	span.SetTag("datasource_id", e.dsInfo.Id)
	span.SetTag("org_id", e.dsInfo.OrgId)

	defer span.Finish()

	opentracing.GlobalTracer().Inject(
		span.Context(),
		opentracing.HTTPHeaders,
		opentracing.HTTPHeadersCarrier(req.Header))

	res, err := ctxhttp.Do(ctx, e.httpClient, req)
	if err != nil {
		queryResult.Error = err
		return queryResult, nil
	}

	data, err := e.unmarshalResponse(res)
	if err != nil {
		queryResult.Error = err
		return queryResult, nil
	}

	err = e.parseResponse(queryResult, data, query)
	if err != nil {
		queryResult.Error = err
		return queryResult, nil
	}

	return queryResult, nil
}

func (e *StackdriverExecutor) unmarshalResponse(res *http.Response) (StackdriverResponse, error) {
	body, err := ioutil.ReadAll(res.Body)
	defer res.Body.Close()
	if err != nil {
		return StackdriverResponse{}, err
	}

	if res.StatusCode/100 != 2 {
		slog.Error("Request failed", "status", res.Status, "body", string(body))
		return StackdriverResponse{}, fmt.Errorf(string(body))
	}

	var data StackdriverResponse
	err = json.Unmarshal(body, &data)
	if err != nil {
		slog.Error("Failed to unmarshal Stackdriver response", "error", err, "status", res.Status, "body", string(body))
		return StackdriverResponse{}, err
	}

	return data, nil
}

func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data StackdriverResponse, query *StackdriverQuery) error {
	metricLabels := make(map[string][]string)
	resourceLabels := make(map[string][]string)

	for _, series := range data.TimeSeries {
		points := make([]tsdb.TimePoint, 0)

		// reverse the order to be ascending
		for i := len(series.Points) - 1; i >= 0; i-- {
			point := series.Points[i]
			points = append(points, tsdb.NewTimePoint(null.FloatFrom(point.Value.DoubleValue), float64((point.Interval.EndTime).Unix())*1000))
		}
		metricName := series.Metric.Type

		for key, value := range series.Metric.Labels {
			if !containsLabel(metricLabels[key], value) {
				metricLabels[key] = append(metricLabels[key], value)
			}
			if len(query.GroupBys) == 0 || containsLabel(query.GroupBys, "metric.label."+key) {
				metricName += " " + value
			}
		}

		for key, value := range series.Resource.Labels {
			if !containsLabel(resourceLabels[key], value) {
				resourceLabels[key] = append(resourceLabels[key], value)
			}

			if containsLabel(query.GroupBys, "resource.label."+key) {
				metricName += " " + value
			}
		}

		queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{
			Name:   metricName,
			Points: points,
		})
	}

	queryRes.Meta.Set("resourceLabels", resourceLabels)
	queryRes.Meta.Set("metricLabels", metricLabels)
	queryRes.Meta.Set("groupBys", query.GroupBys)

	return nil
}

func containsLabel(labels []string, newLabel string) bool {
	for _, val := range labels {
		if val == newLabel {
			return true
		}
	}
	return false
}

func (e *StackdriverExecutor) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) {
	u, _ := url.Parse(dsInfo.Url)
	u.Path = path.Join(u.Path, "render")

	req, err := http.NewRequest(http.MethodGet, "https://monitoring.googleapis.com/", nil)
	if err != nil {
		slog.Info("Failed to create request", "error", err)
		return nil, fmt.Errorf("Failed to create request. error: %v", err)
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion))

	// find plugin
	plugin, ok := plugins.DataSources[dsInfo.Type]
	if !ok {
		return nil, errors.New("Unable to find datasource plugin Stackdriver")
	}
	projectName := dsInfo.JsonData.Get("defaultProject").MustString()
	proxyPass := fmt.Sprintf("stackdriver%s", "v3/projects/"+projectName+"/timeSeries")

	var stackdriverRoute *plugins.AppPluginRoute
	for _, route := range plugin.Routes {
		if route.Path == "stackdriver" {
			stackdriverRoute = route
			break
		}
	}

	pluginproxy.ApplyRoute(ctx, req, proxyPass, stackdriverRoute, dsInfo)

	return req, nil
}

func fixIntervalFormat(target string) string {
	rMinute := regexp.MustCompile(`'(\d+)m'`)
	rMin := regexp.MustCompile("m")
	target = rMinute.ReplaceAllStringFunc(target, func(m string) string {
		return rMin.ReplaceAllString(m, "min")
	})
	rMonth := regexp.MustCompile(`'(\d+)M'`)
	rMon := regexp.MustCompile("M")
	target = rMonth.ReplaceAllStringFunc(target, func(M string) string {
		return rMon.ReplaceAllString(M, "mon")
	})
	return target
}
<|endoftext|>


<commit_before>package http

import (
	"bytes"
	"fmt"
	"net/http"
	"text/template"

	"github.com/VagabondDataNinjas/gizlinebot/domain"
	logrusmiddleware "github.com/andreiashu/echo-logrusmiddleware"
	log "github.com/sirupsen/logrus"

	"github.com/line/line-bot-sdk-go/linebot"

	"github.com/VagabondDataNinjas/gizlinebot/storage"
	"github.com/VagabondDataNinjas/gizlinebot/survey"

	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
)

type ApiConf struct {
	Port int
	// hostname where the API is hosted
	GlobalVars *domain.GlobalTplVars
}

type Api struct {
	Storage *storage.Sql
	LineBot *linebot.Client
	Surv    *survey.Survey
	Conf    *ApiConf
}

func NewApi(s *storage.Sql, lb *linebot.Client, surv *survey.Survey, conf *ApiConf) *Api {
	return &Api{
		Storage: s,
		LineBot: lb,
		Surv:    surv,
		Conf:    conf,
	}
}

// cache-busting middleware
func NoCacheMW(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		c.Response().Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
		return next(c)
	}
}

func (a *Api) Serve() error {
	e := echo.New()
	// Middleware
	e.Logger = logrusmiddleware.Logger{log.StandardLogger()}
	e.Use(logrusmiddleware.Hook())
	e.Use(middleware.Recover())
	// CORS default
	// Allows requests from any origin wth GET, HEAD, PUT, POST or DELETE method.
	e.Use(middleware.CORS())

	qs, err := a.Storage.GetQuestions()
	if err != nil {
		return err
	}
	e.POST("/linewebhook", LineWebhookHandlerBuilder(a.Surv, a.Storage, a.LineBot, a.Conf.GlobalVars))

	e.GET("/api/webform/questions", func(c echo.Context) error {
		return c.JSON(http.StatusOK, qs)
	})

	e.Group("/", NoCacheMW).Static("", "../gizsurvey/build")
	e.Group("/static", NoCacheMW).Static("", "../gizsurvey/build/static")
	e.Group("/media", NoCacheMW).Static("", "../media")

	// lineimgs/groots.png/700
	e.GET("/lineimgs/:filename", LineImgHandler(), NoCacheMW)
	e.GET("/lineimgs/:filename/:size", LineImgHandler(), NoCacheMW)

	e.GET("/api/islands", IslandsHandler(a.Storage), NoCacheMW)

	e.POST("/api/webform/answer", AnswerHandlerBuilder(a.Storage, a.LineBot))
	e.POST("/api/webform/answer-gps", AnswerGpsHandlerBuilder(a.Storage))

	e.POST("/api/login", loginHandler(a.Storage), NoCacheMW)

	r := e.Group("/api/admin")
	jwtSecret, err := a.Storage.GetConfigVal("jwtSecret")
	if err != nil {
		return err
	}
	jwtConfigCookieAuth := middleware.DefaultJWTConfig
	jwtConfigCookieAuth.TokenLookup = "cookie:token"
	jwtConfigCookieAuth.SigningKey = []byte(jwtSecret)
	r.Use(middleware.JWTWithConfig(jwtConfigCookieAuth))

	r.POST("/send-msg", SendLineMsgHandlerBuilder(a.Storage, a.LineBot))
	r.POST("/send/custom/question", SendLineCustomQuestionHandlerBuilder(a.Storage, a.LineBot))

	r.GET("/location-prices", LocationPricesHandlerBuilder(a.Storage))

	r.GET("/download/data.csv", DownloadHandlerBuilder(a.Storage), NoCacheMW)
	r.GET("/download/lineevents.csv", LineEventsDownloadHandlerBuilder(a.Storage), NoCacheMW)
	r.GET("/user/wipe/:userid", WipeUserHandlerBuilder(a.Storage, a.LineBot), NoCacheMW)
	r.GET("/user/prices/:userid", PriceHandler(a.Storage), NoCacheMW)

	log.Fatal(e.Start(fmt.Sprintf(":%d", a.Conf.Port)))
	return nil
}

func PriceHandler(s *storage.Sql) func(c echo.Context) error {
	return func(c echo.Context) error {
		userId := c.Param("userid")
		lp, err := s.GetUserNearbyPrices(userId)
		if err != nil {
			return err
		}

		return c.JSON(http.StatusOK, map[string]interface{}{
			"status": "success", "locs": lp,
		})
	}
}

func userPriceList(userId string, s *storage.Sql) (priceList string, err error) {
	lp, err := s.GetUserNearbyPrices(userId)
	if err != nil {
		return "", err
	}

	if len(lp) == 0 {
		log.Warnf("No prices found for user %s", userId)
		return "", nil
	}

	tplStr, err := s.GetPriceTplMsg()
	if err != nil {
		return "", err
	}
	tmpl, err := template.New("priceMsg").Parse(tplStr)
	if err != nil {
		return "", err
	}

	type TplVars struct {
		Location string
		Price    float64
	}

	var msg string
	for _, loc := range lp {
		buf := new(bytes.Buffer)
		tplVars := TplVars{loc.Name, loc.Price}
		err = tmpl.Execute(buf, tplVars)
		if err != nil {
			return "", err
		}

		msg += buf.String()
		msg += "\n"
	}

	return msg, nil
}

func sendPriceList(bot *linebot.Client, s *storage.Sql, userId string) error {
	priceList, err := userPriceList(userId, s)
	if err != nil {
		return err
	}
	if priceList == "" {
		return nil
	}

	if _, err = bot.PushMessage(userId, linebot.NewTextMessage(priceList)).Do(); err != nil {
		return err
	}
	return nil
}

func sendThankYouMsg(bot *linebot.Client, s *storage.Sql, userId string) error {
	questions, err := s.GetQuestions()
	if err != nil {
		log.Errorf("Error getting questions: %s", err)
		return err
	}

	lastQ, err := questions.Last()
	if err != nil {
		log.Errorf("Error getting last question: %s", err)
		return err
	}

	if _, err = bot.PushMessage(userId, linebot.NewTextMessage(lastQ.Text)).Do(); err != nil {
		log.Errorf("Error sending last thankyou / question: %s", err)
		return err
	}
	return nil
}
<commit_msg>Removed pricehandler<commit_after>package http

import (
	"bytes"
	"fmt"
	"net/http"
	"text/template"

	"github.com/VagabondDataNinjas/gizlinebot/domain"
	logrusmiddleware "github.com/andreiashu/echo-logrusmiddleware"
	log "github.com/sirupsen/logrus"

	"github.com/line/line-bot-sdk-go/linebot"

	"github.com/VagabondDataNinjas/gizlinebot/storage"
	"github.com/VagabondDataNinjas/gizlinebot/survey"

	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
)

type ApiConf struct {
	Port int
	// hostname where the API is hosted
	GlobalVars *domain.GlobalTplVars
}

type Api struct {
	Storage *storage.Sql
	LineBot *linebot.Client
	Surv    *survey.Survey
	Conf    *ApiConf
}

func NewApi(s *storage.Sql, lb *linebot.Client, surv *survey.Survey, conf *ApiConf) *Api {
	return &Api{
		Storage: s,
		LineBot: lb,
		Surv:    surv,
		Conf:    conf,
	}
}

// cache-busting middleware
func NoCacheMW(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		c.Response().Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
		return next(c)
	}
}

func (a *Api) Serve() error {
	e := echo.New()
	// Middleware
	e.Logger = logrusmiddleware.Logger{log.StandardLogger()}
	e.Use(logrusmiddleware.Hook())
	e.Use(middleware.Recover())
	// CORS default
	// Allows requests from any origin wth GET, HEAD, PUT, POST or DELETE method.
	e.Use(middleware.CORS())

	qs, err := a.Storage.GetQuestions()
	if err != nil {
		return err
	}
	e.POST("/linewebhook", LineWebhookHandlerBuilder(a.Surv, a.Storage, a.LineBot, a.Conf.GlobalVars))

	e.GET("/api/webform/questions", func(c echo.Context) error {
		return c.JSON(http.StatusOK, qs)
	})

	e.Group("/", NoCacheMW).Static("", "../gizsurvey/build")
	e.Group("/static", NoCacheMW).Static("", "../gizsurvey/build/static")
	e.Group("/media", NoCacheMW).Static("", "../media")

	// lineimgs/groots.png/700
	e.GET("/lineimgs/:filename", LineImgHandler(), NoCacheMW)
	e.GET("/lineimgs/:filename/:size", LineImgHandler(), NoCacheMW)

	e.GET("/api/islands", IslandsHandler(a.Storage), NoCacheMW)

	e.POST("/api/webform/answer", AnswerHandlerBuilder(a.Storage, a.LineBot))
	e.POST("/api/webform/answer-gps", AnswerGpsHandlerBuilder(a.Storage))

	e.POST("/api/login", loginHandler(a.Storage), NoCacheMW)

	r := e.Group("/api/admin")
	jwtSecret, err := a.Storage.GetConfigVal("jwtSecret")
	if err != nil {
		return err
	}
	jwtConfigCookieAuth := middleware.DefaultJWTConfig
	jwtConfigCookieAuth.TokenLookup = "cookie:token"
	jwtConfigCookieAuth.SigningKey = []byte(jwtSecret)
	r.Use(middleware.JWTWithConfig(jwtConfigCookieAuth))

	r.POST("/send-msg", SendLineMsgHandlerBuilder(a.Storage, a.LineBot))
	r.POST("/send/custom/question", SendLineCustomQuestionHandlerBuilder(a.Storage, a.LineBot))

	r.GET("/location-prices", LocationPricesHandlerBuilder(a.Storage))

	r.GET("/download/data.csv", DownloadHandlerBuilder(a.Storage), NoCacheMW)
	r.GET("/download/lineevents.csv", LineEventsDownloadHandlerBuilder(a.Storage), NoCacheMW)
	r.GET("/user/wipe/:userid", WipeUserHandlerBuilder(a.Storage, a.LineBot), NoCacheMW)

	log.Fatal(e.Start(fmt.Sprintf(":%d", a.Conf.Port)))
	return nil
}

func userPriceList(userId string, s *storage.Sql) (priceList string, err error) {
	lp, err := s.GetUserNearbyPrices(userId)
	if err != nil {
		return "", err
	}

	if len(lp) == 0 {
		log.Warnf("No prices found for user %s", userId)
		return "", nil
	}

	tplStr, err := s.GetPriceTplMsg()
	if err != nil {
		return "", err
	}
	tmpl, err := template.New("priceMsg").Parse(tplStr)
	if err != nil {
		return "", err
	}

	type TplVars struct {
		Location string
		Price    float64
	}

	var msg string
	for _, loc := range lp {
		buf := new(bytes.Buffer)
		tplVars := TplVars{loc.Name, loc.Price}
		err = tmpl.Execute(buf, tplVars)
		if err != nil {
			return "", err
		}

		msg += buf.String()
		msg += "\n"
	}

	return msg, nil
}

func sendPriceList(bot *linebot.Client, s *storage.Sql, userId string) error {
	priceList, err := userPriceList(userId, s)
	if err != nil {
		return err
	}
	if priceList == "" {
		return nil
	}

	if _, err = bot.PushMessage(userId, linebot.NewTextMessage(priceList)).Do(); err != nil {
		return err
	}
	return nil
}

func sendThankYouMsg(bot *linebot.Client, s *storage.Sql, userId string) error {
	questions, err := s.GetQuestions()
	if err != nil {
		log.Errorf("Error getting questions: %s", err)
		return err
	}

	lastQ, err := questions.Last()
	if err != nil {
		log.Errorf("Error getting last question: %s", err)
		return err
	}

	if _, err = bot.PushMessage(userId, linebot.NewTextMessage(lastQ.Text)).Do(); err != nil {
		log.Errorf("Error sending last thankyou / question: %s", err)
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"io"
	"os"
	"os/exec"
	"os/user"
	"strconv"
	"strings"
	"time"

	"github.com/fatih/color"
	"github.com/jroimartin/gocui"
)

var state = &State{
	ActionIndex: -1,
	Settings:    &Settings{},
	HideHelp:    len(os.Args) > 1,
}

// State is the central function managing the information of claws.
type State struct {
	// important to running the application as a whole
	ActionIndex int
	Mode        int
	Writer      io.Writer
	Conn        *WebSocket

	// important for drawing
	FirstDrawDone     bool
	ShouldQuit        bool
	HideHelp          bool
	KeepAutoscrolling bool

	// functions
	ExecuteFunc func(func(*gocui.Gui) error)

	Settings *Settings
}

// PushAction adds an action to LastActions
func (s *State) PushAction(act string) {
	s.Settings.LastActions = append([]string{act}, s.Settings.LastActions...)
	if len(s.Settings.LastActions) > 100 {
		s.Settings.LastActions = s.Settings.LastActions[:100]
	}
	s.Settings.Save()
}

// BrowseActions changes the ActionIndex and returns the value at the specified index.
// move is the number of elements to move (negatives go into more recent history,
// 0 returns the current element, positives go into older history)
func (s *State) BrowseActions(move int) string {
	s.ActionIndex += move
	if s.ActionIndex >= len(s.Settings.LastActions) {
		s.ActionIndex = len(s.Settings.LastActions) - 1
	} else if s.ActionIndex < -1 {
		s.ActionIndex = -1
	}

	// -1 always indicates the "next" element, thus empty
	if s.ActionIndex == -1 {
		return ""
	}
	return s.Settings.LastActions[s.ActionIndex]
}

// StartConnection begins a WebSocket connection to url.
func (s *State) StartConnection(url string) error {
	if s.Conn != nil {
		return errors.New("state: conn is not nil")
	}
	ws, err := CreateWebSocket(url)
	if err != nil {
		return err
	}
	s.Conn = ws
	connectionStarted = time.Now()
	go s.wsReader()
	return nil
}

func (s *State) wsReader() {
	ch := s.Conn.ReadChannel()
	for msg := range ch {
		if s.Settings.JSONFormatting {
			msg = attemptJSONFormatting(msg)
		}
		s.Server(msg)
	}
}

var (
	printDebug  = color.New(color.FgCyan).Fprint
	printError  = color.New(color.FgRed).Fprint
	printUser   = color.New(color.FgGreen).Fprint
	printServer = color.New(color.FgWhite).Fprint
)

// Debug prints debug information to the Writer, using light blue.
func (s *State) Debug(x string) {
	s.printToOut(printDebug, x)
}

// Error prints an error to the Writer, using red.
func (s *State) Error(x string) {
	s.printToOut(printError, x)
}

// User prints user-provided messages to the Writer, using green.
func (s *State) User(x string) {
	res, err := s.pipe(x, "out", s.Settings.Pipe.Out)
	if err != nil {
		s.Error(err.Error())
		if res == "" || res == "\n" {
			return
		}
	}
	s.printToOut(printUser, res)
}

// Server prints server-returned messages to the Writer, using white.
func (s *State) Server(x string) {
	res, err := s.pipe(x, "in", s.Settings.Pipe.In)
	if err != nil {
		s.Error(err.Error())
		if res == "" || res == "\n" {
			return
		}
	}
	s.printToOut(printServer, x)
}

var (
	sessionStarted    = time.Now()
	connectionStarted time.Time
)

func (s *State) pipe(data, t string, command []string) (string, error) {
	if len(command) < 1 {
		return data, nil
	}
	// prepare the command: create it, set up env variables
	c := exec.Command(command[0], command[1:]...)
	c.Env = append(
		os.Environ(),
		"CLAWS_PIPE_TYPE="+t,
		"CLAWS_SESSION="+strconv.FormatInt(sessionStarted.UnixNano(), 10),
		"CLAWS_CONNECTION="+strconv.FormatInt(sessionStarted.UnixNano(), 10),
	)
	// set up stdin
	stdin := strings.NewReader(data)
	c.Stdin = stdin

	// run the command
	res, err := c.Output()
	return string(res), err
}

func (s *State) printToOut(f func(io.Writer, ...interface{}) (int, error), str string) {
	if s.Settings.Timestamp != "" {
		str = time.Now().Format(s.Settings.Timestamp) + str
	}

	if str != "" && str[len(str)-1] != '\n' {
		str += "\n"
	}
	s.ExecuteFunc(func(*gocui.Gui) error {
		_, err := f(s.Writer, str)
		return err
	})
}

// Settings contains persistent information about the usage of claws.
type Settings struct {
	JSONFormatting   bool
	Timestamp        string
	LastWebsocketURL string
	LastActions      []string
	Pipe             struct {
		In  []string
		Out []string
	}
}

// Load loads settings from ~/.config/claws.json
func (s *Settings) Load() error {
	folder, err := getConfigFolder()
	if err != nil {
		return err
	}

	f, err := os.Open(folder + "claws.json")
	if err != nil {
		// silently ignore ErrNotExist
		if os.IsNotExist(err) {
			return nil
		}
		return err
	}
	defer f.Close()

	return json.NewDecoder(f).Decode(s)
}

// Save saves settings to ~/.config/claws.json
func (s Settings) Save() error {
	folder, err := getConfigFolder()
	if err != nil {
		return err
	}

	f, err := os.Create(folder + "claws.json")
	if err != nil {
		return err
	}
	defer f.Close()

	e := json.NewEncoder(f)
	e.SetIndent("", "\t")
	return e.Encode(s)
}

func getConfigFolder() (string, error) {
	u, err := user.Current()
	if err != nil {
		return "", err
	}
	folder := u.HomeDir + "/.config/"

	err = os.MkdirAll(folder, 0755)

	return folder, err
}
<commit_msg>move attempt to format json into server message printing<commit_after>package main

import (
	"encoding/json"
	"errors"
	"io"
	"os"
	"os/exec"
	"os/user"
	"strconv"
	"strings"
	"time"

	"github.com/fatih/color"
	"github.com/jroimartin/gocui"
)

var state = &State{
	ActionIndex: -1,
	Settings:    &Settings{},
	HideHelp:    len(os.Args) > 1,
}

// State is the central function managing the information of claws.
type State struct {
	// important to running the application as a whole
	ActionIndex int
	Mode        int
	Writer      io.Writer
	Conn        *WebSocket

	// important for drawing
	FirstDrawDone     bool
	ShouldQuit        bool
	HideHelp          bool
	KeepAutoscrolling bool

	// functions
	ExecuteFunc func(func(*gocui.Gui) error)

	Settings *Settings
}

// PushAction adds an action to LastActions
func (s *State) PushAction(act string) {
	s.Settings.LastActions = append([]string{act}, s.Settings.LastActions...)
	if len(s.Settings.LastActions) > 100 {
		s.Settings.LastActions = s.Settings.LastActions[:100]
	}
	s.Settings.Save()
}

// BrowseActions changes the ActionIndex and returns the value at the specified index.
// move is the number of elements to move (negatives go into more recent history,
// 0 returns the current element, positives go into older history)
func (s *State) BrowseActions(move int) string {
	s.ActionIndex += move
	if s.ActionIndex >= len(s.Settings.LastActions) {
		s.ActionIndex = len(s.Settings.LastActions) - 1
	} else if s.ActionIndex < -1 {
		s.ActionIndex = -1
	}

	// -1 always indicates the "next" element, thus empty
	if s.ActionIndex == -1 {
		return ""
	}
	return s.Settings.LastActions[s.ActionIndex]
}

// StartConnection begins a WebSocket connection to url.
func (s *State) StartConnection(url string) error {
	if s.Conn != nil {
		return errors.New("state: conn is not nil")
	}
	ws, err := CreateWebSocket(url)
	if err != nil {
		return err
	}
	s.Conn = ws
	connectionStarted = time.Now()
	go s.wsReader()
	return nil
}

func (s *State) wsReader() {
	ch := s.Conn.ReadChannel()
	for msg := range ch {
		s.Server(msg)
	}
}

var (
	printDebug  = color.New(color.FgCyan).Fprint
	printError  = color.New(color.FgRed).Fprint
	printUser   = color.New(color.FgGreen).Fprint
	printServer = color.New(color.FgWhite).Fprint
)

// Debug prints debug information to the Writer, using light blue.
func (s *State) Debug(x string) {
	s.printToOut(printDebug, x)
}

// Error prints an error to the Writer, using red.
func (s *State) Error(x string) {
	s.printToOut(printError, x)
}

// User prints user-provided messages to the Writer, using green.
func (s *State) User(x string) {
	res, err := s.pipe(x, "out", s.Settings.Pipe.Out)
	if err != nil {
		s.Error(err.Error())
		if res == "" || res == "\n" {
			return
		}
	}
	s.printToOut(printUser, res)
}

// Server prints server-returned messages to the Writer, using white.
func (s *State) Server(x string) {
	res, err := s.pipe(x, "in", s.Settings.Pipe.In)
	if err != nil {
		s.Error(err.Error())
		if res == "" || res == "\n" {
			return
		}
	}
	if s.Settings.JSONFormatting {
		res = attemptJSONFormatting(res)
	}
	s.printToOut(printServer, x)
}

var (
	sessionStarted    = time.Now()
	connectionStarted time.Time
)

func (s *State) pipe(data, t string, command []string) (string, error) {
	if len(command) < 1 {
		return data, nil
	}
	// prepare the command: create it, set up env variables
	c := exec.Command(command[0], command[1:]...)
	c.Env = append(
		os.Environ(),
		"CLAWS_PIPE_TYPE="+t,
		"CLAWS_SESSION="+strconv.FormatInt(sessionStarted.UnixNano(), 10),
		"CLAWS_CONNECTION="+strconv.FormatInt(sessionStarted.UnixNano(), 10),
	)
	// set up stdin
	stdin := strings.NewReader(data)
	c.Stdin = stdin

	// run the command
	res, err := c.Output()
	return string(res), err
}

func (s *State) printToOut(f func(io.Writer, ...interface{}) (int, error), str string) {
	if s.Settings.Timestamp != "" {
		str = time.Now().Format(s.Settings.Timestamp) + str
	}

	if str != "" && str[len(str)-1] != '\n' {
		str += "\n"
	}
	s.ExecuteFunc(func(*gocui.Gui) error {
		_, err := f(s.Writer, str)
		return err
	})
}

// Settings contains persistent information about the usage of claws.
type Settings struct {
	JSONFormatting   bool
	Timestamp        string
	LastWebsocketURL string
	LastActions      []string
	Pipe             struct {
		In  []string
		Out []string
	}
}

// Load loads settings from ~/.config/claws.json
func (s *Settings) Load() error {
	folder, err := getConfigFolder()
	if err != nil {
		return err
	}

	f, err := os.Open(folder + "claws.json")
	if err != nil {
		// silently ignore ErrNotExist
		if os.IsNotExist(err) {
			return nil
		}
		return err
	}
	defer f.Close()

	return json.NewDecoder(f).Decode(s)
}

// Save saves settings to ~/.config/claws.json
func (s Settings) Save() error {
	folder, err := getConfigFolder()
	if err != nil {
		return err
	}

	f, err := os.Create(folder + "claws.json")
	if err != nil {
		return err
	}
	defer f.Close()

	e := json.NewEncoder(f)
	e.SetIndent("", "\t")
	return e.Encode(s)
}

func getConfigFolder() (string, error) {
	u, err := user.Current()
	if err != nil {
		return "", err
	}
	folder := u.HomeDir + "/.config/"

	err = os.MkdirAll(folder, 0755)

	return folder, err
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"log"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"runtime"
	"strings"
	"syscall"

	"net/http"
)

func main() {

	runtime.GOMAXPROCS(2)
	log.Printf("at=main, MaxParallelism=%d\n", MaxParallelism())

	exhibitor := flag.Bool("exhibitor", false, "use EXHIBITOR_URL from env to lookup seed brokers")
	brokers := flag.String("brokers", "", "comma seperated list of ip:port to use as seed brokers")
	db := flag.String("db", "events.db", "name of the boltdb database file")
	port := flag.String("port", "3887", "port on which to listen for events")
	debug := flag.Bool("debug", false, "start a pprof http server on 6060")

	flag.Parse()

	if *debug {
		go func() {
			http.ListenAndServe("localhost:6060", nil)
		}()
	}

	exitChan := make(chan os.Signal)

	signal.Notify(exitChan, syscall.SIGHUP,
		syscall.SIGINT,
		syscall.SIGTERM,
		syscall.SIGQUIT)

	store, err := OpenStore(*db)
	if err != nil {
		log.Panicf("unable to open events.db, exiting! %v\n", err)
	}

	store.db.NoSync = true

	var brokerList []string

	if *exhibitor {
		rb, err := KafkaSeedBrokers(os.Getenv("EXHIBITOR_URL"), "kafka")
		if err != nil {
			log.Panicf("unable to get Kafka Seed Brokers, exiting! %v\n", err)
		}
		brokerList = rb
	} else {
		brokerList = strings.Split(*brokers, ",")
	}

	deliver, err := NewKafkaDeliver(store, "test", brokerList)
	if err != nil {
		log.Panicf("unable to create KafkaDeliver, exiting! %v\n", err)
	}

	deliver.Start()
	StartEndpoint(*port, store)

	select {
	case sig := <-exitChan:
		log.Printf("go=main at=received-signal signal=%s\n", sig)
		err := store.Close()
		deliver.Stop()
		if err != nil {
			log.Printf("go=main at=store-close-error error=%s\n", err)
			os.Exit(1)
		} else {
			log.Printf("go=main at=store-closed-cleanly \n")
		}
	}

}

type EventIn struct {
	event *Event
	saved chan bool
}

type EventOut struct {
	event    *Event
	sequence int64
}

type Event struct {
	Channel string
	Body    []byte
}

func MaxParallelism() int {
	maxProcs := runtime.GOMAXPROCS(0)
	numCPU := runtime.NumCPU()
	if maxProcs < numCPU {
		return maxProcs
	}
	return numCPU
}
<commit_msg>set log discard option<commit_after>package main

import (
	"flag"
	"io/ioutil"
	"log"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"runtime"
	"strings"
	"syscall"

	"net/http"
)

func main() {

	runtime.GOMAXPROCS(2)
	log.Printf("at=main, MaxParallelism=%d\n", MaxParallelism())

	exhibitor := flag.Bool("exhibitor", false, "use EXHIBITOR_URL from env to lookup seed brokers")
	brokers := flag.String("brokers", "", "comma seperated list of ip:port to use as seed brokers")
	db := flag.String("db", "events.db", "name of the boltdb database file")
	port := flag.String("port", "3887", "port on which to listen for events")
	debug := flag.Bool("debug", false, "start a pprof http server on 6060")

	flag.Parse()

	if *debug {
		go func() {
			http.ListenAndServe("localhost:6060", nil)
		}()
	}

	if !*debug {
		log.SetOutput(ioutil.Discard)
	}

	exitChan := make(chan os.Signal)

	signal.Notify(exitChan, syscall.SIGHUP,
		syscall.SIGINT,
		syscall.SIGTERM,
		syscall.SIGQUIT)

	store, err := OpenStore(*db)
	if err != nil {
		log.Panicf("unable to open events.db, exiting! %v\n", err)
	}

	var brokerList []string

	if *exhibitor {
		rb, err := KafkaSeedBrokers(os.Getenv("EXHIBITOR_URL"), "kafka")
		if err != nil {
			log.Panicf("unable to get Kafka Seed Brokers, exiting! %v\n", err)
		}
		brokerList = rb
	} else {
		brokerList = strings.Split(*brokers, ",")
	}

	deliver, err := NewKafkaDeliver(store, "test", brokerList)
	if err != nil {
		log.Panicf("unable to create KafkaDeliver, exiting! %v\n", err)
	}

	deliver.Start()
	StartEndpoint(*port, store)

	select {
	case sig := <-exitChan:
		log.Printf("go=main at=received-signal signal=%s\n", sig)
		err := store.Close()
		deliver.Stop()
		if err != nil {
			log.Printf("go=main at=store-close-error error=%s\n", err)
			os.Exit(1)
		} else {
			log.Printf("go=main at=store-closed-cleanly \n")
		}
	}

}

type EventIn struct {
	event *Event
	saved chan bool
}

type EventOut struct {
	event    *Event
	sequence int64
}

type Event struct {
	Channel string
	Body    []byte
}

func MaxParallelism() int {
	maxProcs := runtime.GOMAXPROCS(0)
	numCPU := runtime.NumCPU()
	if maxProcs < numCPU {
		return maxProcs
	}
	return numCPU
}
<|endoftext|>


<commit_before>// 30 march 2014

package ui

/*
Mac OS X uses its own set of hardware key codes that are different from PC keyboard scancodes, but are positional (like PC keyboard scancodes). These are defined in <HIToolbox/Events.h>, a Carbon header. As far as I can tell, there's no way to include this header without either using an absolute path or linking Carbon into the program, so the constant values are used here instead.

The Cocoa docs do guarantee that -[NSEvent keyCode] results in key codes that are the same as those returned by Carbon; that is, these codes.
*/

// use uintptr to be safe
var keycodeKeys = map[uintptr]byte{
	0x00:	'a',
	0x01:	's',
	0x02:	'd',
	0x03:	'f',
	0x04:	'h',
	0x05:	'g',
	0x06:	'z',
	0x07:	'x',
	0x08:	'c',
	0x09:	'v',
	0x0B:	'b',
	0x0C:	'q',
	0x0D:	'w',
	0x0E:	'e',
	0x0F:	'r',
	0x10:	'y',
	0x11:	't',
	0x12:	'1',
	0x13:	'2',
	0x14:	'3',
	0x15:	'4',
	0x16:	'6',
	0x17:	'5',
	0x18:	'=',
	0x19:	'9',
	0x1A:	'7',
	0x1B:	'-',
	0x1C:	'8',
	0x1D:	'0',
	0x1E:	']',
	0x1F:	'o',
	0x20:	'u',
	0x21:	'[',
	0x22:	'i',
	0x23:	'p',
	0x25:	'l',
	0x26:	'j',
	0x27:	'\'',
	0x28:	'k',
	0x29:	';',
	0x2A:	'\\',
	0x2B:	',',
	0x2C:	'/',
	0x2D:	'n',
	0x2E:	'm',
	0x2F:	'.',
	0x32:	'`',
	0x24:	'\n',
	0x30:	'\t',
	0x31:	' ',
	0x33:	'\b',
}

var keycodeExtKeys = map[uintptr]ExtKey{
	0x41:	NDot,
	0x43:	NMultiply,
	0x45:	NAdd,
//	0x47:	kVK_ANSI_KeypadClear,
	0x4B:	NDivide,
	0x4C:	NEnter,
	0x4E:	NSubtract,
	0x52:	N0,
	0x53:	N1,
	0x54:	N2,
	0x55:	N3,
	0x56:	N4,
	0x57:	N5,
	0x58:	N6,
	0x59:	N7,
	0x5B:	N8,
	0x5C:	N9,
	0x35:	Escape,
	0x60:	F5,
	0x61:	F6,
	0x62:	F7,
	0x63:	F3,
	0x64:	F8,
	0x65:	F9,
	0x67:	F11,
	0x6D:	F10,
	0x6F:	F12,
//	0x72:	kVK_Help,
	0x73:	Home,
	0x74:	PageUp,
	0x75:	Delete,
	0x76:	F4,
	0x77:	End,
	0x78:	F2,
	0x79:	PageDown,
	0x7A:	F1,
	0x7B:	Left,
	0x7C:	Right,
	0x7D:	Down,
	0x7E:	Up,
}

var keycodeModifiers = map[uintptr]Modifiers{
	0x37:	Ctrl,		// left command (TODO both commands?)
	0x38:	Shift,		// left shift
	0x3A:	Alt,		// left option
//	0x3B:	kVK_Control,
	0x3C:	Shift,		// right shift
	0x3D:	Alt,		// right alt
//	0x3E:	kVK_RightControl,
}

func fromKeycode(keycode uintptr) (ke KeyEvent, ok bool) {
	if key, ok := keycodeKeys[keycode]; ok {
		ke.Key = key
		return ke, true
	}
	if extkey, ok := keycodeExtKeys[keycode]; ok {
		ke.ExtKey = extkey
		return ke, true
	}
	return ke, false
}
<commit_msg>Added right Command to events_darwin.go.<commit_after>// 30 march 2014

package ui

/*
Mac OS X uses its own set of hardware key codes that are different from PC keyboard scancodes, but are positional (like PC keyboard scancodes). These are defined in <HIToolbox/Events.h>, a Carbon header. As far as I can tell, there's no way to include this header without either using an absolute path or linking Carbon into the program, so the constant values are used here instead.

The Cocoa docs do guarantee that -[NSEvent keyCode] results in key codes that are the same as those returned by Carbon; that is, these codes.
*/

// use uintptr to be safe
var keycodeKeys = map[uintptr]byte{
	0x00:	'a',
	0x01:	's',
	0x02:	'd',
	0x03:	'f',
	0x04:	'h',
	0x05:	'g',
	0x06:	'z',
	0x07:	'x',
	0x08:	'c',
	0x09:	'v',
	0x0B:	'b',
	0x0C:	'q',
	0x0D:	'w',
	0x0E:	'e',
	0x0F:	'r',
	0x10:	'y',
	0x11:	't',
	0x12:	'1',
	0x13:	'2',
	0x14:	'3',
	0x15:	'4',
	0x16:	'6',
	0x17:	'5',
	0x18:	'=',
	0x19:	'9',
	0x1A:	'7',
	0x1B:	'-',
	0x1C:	'8',
	0x1D:	'0',
	0x1E:	']',
	0x1F:	'o',
	0x20:	'u',
	0x21:	'[',
	0x22:	'i',
	0x23:	'p',
	0x25:	'l',
	0x26:	'j',
	0x27:	'\'',
	0x28:	'k',
	0x29:	';',
	0x2A:	'\\',
	0x2B:	',',
	0x2C:	'/',
	0x2D:	'n',
	0x2E:	'm',
	0x2F:	'.',
	0x32:	'`',
	0x24:	'\n',
	0x30:	'\t',
	0x31:	' ',
	0x33:	'\b',
}

var keycodeExtKeys = map[uintptr]ExtKey{
	0x41:	NDot,
	0x43:	NMultiply,
	0x45:	NAdd,
//	0x47:	kVK_ANSI_KeypadClear,
	0x4B:	NDivide,
	0x4C:	NEnter,
	0x4E:	NSubtract,
	0x52:	N0,
	0x53:	N1,
	0x54:	N2,
	0x55:	N3,
	0x56:	N4,
	0x57:	N5,
	0x58:	N6,
	0x59:	N7,
	0x5B:	N8,
	0x5C:	N9,
	0x35:	Escape,
	0x60:	F5,
	0x61:	F6,
	0x62:	F7,
	0x63:	F3,
	0x64:	F8,
	0x65:	F9,
	0x67:	F11,
	0x6D:	F10,
	0x6F:	F12,
//	0x72:	kVK_Help,
	0x73:	Home,
	0x74:	PageUp,
	0x75:	Delete,
	0x76:	F4,
	0x77:	End,
	0x78:	F2,
	0x79:	PageDown,
	0x7A:	F1,
	0x7B:	Left,
	0x7C:	Right,
	0x7D:	Down,
	0x7E:	Up,
}

var keycodeModifiers = map[uintptr]Modifiers{
	0x37:	Ctrl,		// left command
	0x38:	Shift,		// left shift
	0x3A:	Alt,		// left option
//	0x3B:	kVK_Control,
	0x3C:	Shift,		// right shift
	0x3D:	Alt,		// right alt
//	0x3E:	kVK_RightControl,

	// the following is not in Events.h for some reason
	// thanks to Nicole from irc.badnik.net
	0x36:	Ctrl,		// right command
}

func fromKeycode(keycode uintptr) (ke KeyEvent, ok bool) {
	if key, ok := keycodeKeys[keycode]; ok {
		ke.Key = key
		return ke, true
	}
	if extkey, ok := keycodeExtKeys[keycode]; ok {
		ke.ExtKey = extkey
		return ke, true
	}
	return ke, false
}
<|endoftext|>


<commit_before>package generate

var modelTpl = `package {{.PackageName}}

// Code generated; DO NOT EDIT.

import (
	"errors"
	{{ if .Generate "JoinSQL"}}
	"fmt"
	"strings"
	{{ end }}

	"github.com/Nivl/go-rest-tools/types/apierror"
	{{ if or (.Generate "doCreate") (.Generate "doUpdate") }}"github.com/Nivl/go-types/datetime"{{ end }}
	"github.com/Nivl/go-sqldb"
	{{ if .UseUUID }}uuid "github.com/satori/go.uuid"{{ end }}
)

{{ if .Generate "JoinSQL" -}}
// Join{{.OptionalName}}SQL returns a string ready to be embed in a JOIN query
func Join{{.OptionalName}}SQL(prefix string) string {
	fields := []string{ {{.FieldsAsArray}} }
	output := ""

	for _, field := range fields {
		fullName := fmt.Sprintf("%s.%s", prefix, field)
		output += fmt.Sprintf("%s \"%s\", ", fullName, fullName)
	}
	return strings.TrimSuffix(output, ", ")
}
{{- end }}

{{ if .Generate "Get" -}}
// Get{{.OptionalName}}ByID finds and returns an active {{.ModelNameLC}} by ID
// Deleted object are not returned
func Get{{.OptionalName}}ByID(q sqldb.Queryable, id string) (*{{.ModelName}}, error) {
	{{.ModelVar}} := &{{.ModelName}}{}
	stmt := "SELECT * from {{.TableName}} WHERE id=$1 and deleted_at IS NULL LIMIT 1"
	err := q.Get({{.ModelVar}}, stmt, id)
	return {{.ModelVar}}, apierror.NewFromSQL(err)
}
{{- end }}

{{ if .Generate "GetAny" -}}
// GetAny{{.OptionalName}}ByID finds and returns an {{.ModelNameLC}} by ID.
// Deleted object are returned
func GetAny{{.OptionalName}}ByID(q sqldb.Queryable, id string) (*{{.ModelName}}, error) {
	{{.ModelVar}} := &{{.ModelName}}{}
	stmt := "SELECT * from {{.TableName}} WHERE id=$1 LIMIT 1"
	err := q.Get({{.ModelVar}}, stmt, id)
	return {{.ModelVar}}, apierror.NewFromSQL(err)
}
{{- end }}

{{ if and (.Generate "Save") (.UseUUID) -}}
// Save creates or updates the article depending on the value of the id using
// a transaction
func ({{.ModelVar}} *{{.ModelName}}) Save(q sqldb.Queryable) error {
	if {{.ModelVar}}.ID == "" {
		return {{.ModelVar}}.Create(q)
	}

	return {{.ModelVar}}.Update(q)
}
{{- end }}

{{ if .Generate "Create" -}}
// Create persists a {{.ModelNameLC}} in the database
func ({{.ModelVar}} *{{.ModelName}}) Create(q sqldb.Queryable) error {
	{{ if .UseUUID }}
	if {{.ModelVar}}.ID != "" {
		return errors.New("cannot persist a {{.ModelNameLC}} that already has an ID")
	}
	{{- end }}

	return {{.ModelVar}}.doCreate(q)
}
{{- end }}

{{ if .Generate "doCreate" -}}
// doCreate persists a {{.ModelNameLC}} in the database using a Node
func ({{.ModelVar}} *{{.ModelName}}) doCreate(q sqldb.Queryable) error {
	{{ if .UseUUID }}{{.ModelVar}}.ID = uuid.NewV4().String(){{ end }}
	{{.ModelVar}}.UpdatedAt = datetime.Now()
	if {{.ModelVar}}.CreatedAt == nil {
		{{.ModelVar}}.CreatedAt = datetime.Now()
	}

	stmt := "{{.CreateStmt}}"
	_, err := q.NamedExec(stmt, {{.ModelVar}})

  return apierror.NewFromSQL(err)
}
{{- end }}

{{ if .Generate "Update" -}}
// Update updates most of the fields of a persisted {{.ModelNameLC}}
// Excluded fields are id, created_at, deleted_at, etc.
func ({{.ModelVar}} *{{.ModelName}}) Update(q sqldb.Queryable) error {
	if {{.ModelVar}}.ID == "" {
		return errors.New("cannot update a non-persisted {{.ModelNameLC}}")
	}

	return {{.ModelVar}}.doUpdate(q)
}
{{- end }}

{{ if .Generate "doUpdate" -}}
// doUpdate updates a {{.ModelNameLC}} in the database
func ({{.ModelVar}} *{{.ModelName}}) doUpdate(q sqldb.Queryable) error {
	if {{.ModelVar}}.ID == "" {
		return errors.New("cannot update a non-persisted {{.ModelNameLC}}")
	}

	{{.ModelVar}}.UpdatedAt = datetime.Now()

	stmt := "{{.UpdateStmt}}"
	_, err := q.NamedExec(stmt, {{.ModelVar}})

	return apierror.NewFromSQL(err)
}
{{- end }}

{{ if .Generate "Delete" -}}
// Delete removes a {{.ModelNameLC}} from the database
func ({{.ModelVar}} *{{.ModelName}}) Delete(q sqldb.Queryable) error {
	if {{.ModelVar}}.ID == "" {
		return errors.New("{{.ModelNameLC}} has not been saved")
	}

	stmt := "DELETE FROM {{.TableName}} WHERE id=$1"
	_, err := q.Exec(stmt, {{.ModelVar}}.ID)

	return err
}
{{- end }}

{{ if .Generate "IsZero" -}}
// IsZero checks if the object is either nil or don't have an ID
func ({{.ModelVar}} *{{.ModelName}}) IsZero() bool {
	return {{.ModelVar}} == nil || {{.ModelVar}}.ID == ""
}
{{- end }}`
<commit_msg>use apperror instead of apierror<commit_after>package generate

var modelTpl = `package {{.PackageName}}

// Code generated; DO NOT EDIT.

import (
	"errors"
	{{ if .Generate "JoinSQL"}}
	"fmt"
	"strings"
	{{ end }}

	"github.com/Nivl/go-rest-tools/types/apperror"
	{{ if or (.Generate "doCreate") (.Generate "doUpdate") }}"github.com/Nivl/go-types/datetime"{{ end }}
	"github.com/Nivl/go-sqldb"
	{{ if .UseUUID }}uuid "github.com/satori/go.uuid"{{ end }}
)

{{ if .Generate "JoinSQL" -}}
// Join{{.OptionalName}}SQL returns a string ready to be embed in a JOIN query
func Join{{.OptionalName}}SQL(prefix string) string {
	fields := []string{ {{.FieldsAsArray}} }
	output := ""

	for _, field := range fields {
		fullName := fmt.Sprintf("%s.%s", prefix, field)
		output += fmt.Sprintf("%s \"%s\", ", fullName, fullName)
	}
	return strings.TrimSuffix(output, ", ")
}
{{- end }}

{{ if .Generate "Get" -}}
// Get{{.OptionalName}}ByID finds and returns an active {{.ModelNameLC}} by ID
// Deleted object are not returned
func Get{{.OptionalName}}ByID(q sqldb.Queryable, id string) (*{{.ModelName}}, error) {
	{{.ModelVar}} := &{{.ModelName}}{}
	stmt := "SELECT * from {{.TableName}} WHERE id=$1 and deleted_at IS NULL LIMIT 1"
	err := q.Get({{.ModelVar}}, stmt, id)
	return {{.ModelVar}}, apperror.NewFromSQL(err)
}
{{- end }}

{{ if .Generate "GetAny" -}}
// GetAny{{.OptionalName}}ByID finds and returns an {{.ModelNameLC}} by ID.
// Deleted object are returned
func GetAny{{.OptionalName}}ByID(q sqldb.Queryable, id string) (*{{.ModelName}}, error) {
	{{.ModelVar}} := &{{.ModelName}}{}
	stmt := "SELECT * from {{.TableName}} WHERE id=$1 LIMIT 1"
	err := q.Get({{.ModelVar}}, stmt, id)
	return {{.ModelVar}}, apperror.NewFromSQL(err)
}
{{- end }}

{{ if and (.Generate "Save") (.UseUUID) -}}
// Save creates or updates the article depending on the value of the id using
// a transaction
func ({{.ModelVar}} *{{.ModelName}}) Save(q sqldb.Queryable) error {
	if {{.ModelVar}}.ID == "" {
		return {{.ModelVar}}.Create(q)
	}

	return {{.ModelVar}}.Update(q)
}
{{- end }}

{{ if .Generate "Create" -}}
// Create persists a {{.ModelNameLC}} in the database
func ({{.ModelVar}} *{{.ModelName}}) Create(q sqldb.Queryable) error {
	{{ if .UseUUID }}
	if {{.ModelVar}}.ID != "" {
		return errors.New("cannot persist a {{.ModelNameLC}} that already has an ID")
	}
	{{- end }}

	return {{.ModelVar}}.doCreate(q)
}
{{- end }}

{{ if .Generate "doCreate" -}}
// doCreate persists a {{.ModelNameLC}} in the database using a Node
func ({{.ModelVar}} *{{.ModelName}}) doCreate(q sqldb.Queryable) error {
	{{ if .UseUUID }}{{.ModelVar}}.ID = uuid.NewV4().String(){{ end }}
	{{.ModelVar}}.UpdatedAt = datetime.Now()
	if {{.ModelVar}}.CreatedAt == nil {
		{{.ModelVar}}.CreatedAt = datetime.Now()
	}

	stmt := "{{.CreateStmt}}"
	_, err := q.NamedExec(stmt, {{.ModelVar}})

  return apperror.NewFromSQL(err)
}
{{- end }}

{{ if .Generate "Update" -}}
// Update updates most of the fields of a persisted {{.ModelNameLC}}
// Excluded fields are id, created_at, deleted_at, etc.
func ({{.ModelVar}} *{{.ModelName}}) Update(q sqldb.Queryable) error {
	if {{.ModelVar}}.ID == "" {
		return errors.New("cannot update a non-persisted {{.ModelNameLC}}")
	}

	return {{.ModelVar}}.doUpdate(q)
}
{{- end }}

{{ if .Generate "doUpdate" -}}
// doUpdate updates a {{.ModelNameLC}} in the database
func ({{.ModelVar}} *{{.ModelName}}) doUpdate(q sqldb.Queryable) error {
	if {{.ModelVar}}.ID == "" {
		return errors.New("cannot update a non-persisted {{.ModelNameLC}}")
	}

	{{.ModelVar}}.UpdatedAt = datetime.Now()

	stmt := "{{.UpdateStmt}}"
	_, err := q.NamedExec(stmt, {{.ModelVar}})

	return apperror.NewFromSQL(err)
}
{{- end }}

{{ if .Generate "Delete" -}}
// Delete removes a {{.ModelNameLC}} from the database
func ({{.ModelVar}} *{{.ModelName}}) Delete(q sqldb.Queryable) error {
	if {{.ModelVar}}.ID == "" {
		return errors.New("{{.ModelNameLC}} has not been saved")
	}

	stmt := "DELETE FROM {{.TableName}} WHERE id=$1"
	_, err := q.Exec(stmt, {{.ModelVar}}.ID)

	return err
}
{{- end }}

{{ if .Generate "IsZero" -}}
// IsZero checks if the object is either nil or don't have an ID
func ({{.ModelVar}} *{{.ModelName}}) IsZero() bool {
	return {{.ModelVar}} == nil || {{.ModelVar}}.ID == ""
}
{{- end }}`
<|endoftext|>


<commit_before>package ultron

import (
	"sort"
	"strconv"
	"sync"
	"sync/atomic"
	"time"
)

var timeDistributions = [10]float64{0.5, 0.6, 0.7, 0.8, 0.9, 0.95, 0.97, 0.98, 0.99, 1.0}

type (
	roundedMillisecond int64

	statsEntry struct {
		name              string                       // 统计对象名称
		numRequests       int64                        // 成功请求次数
		numFailures       int64                        // 失败次数
		totalResponseTime time.Duration                // 成功请求的response time总和，基于原始的响应时间
		minResponseTime   time.Duration                // 最快的响应时间
		maxResponseTime   time.Duration                // 最慢的响应时间
		trend             map[int64]int64              // 按时间轴（秒级）记录成功请求次数
		failuresTrend     map[int64]int64              // 按时间轴（妙计）记录错误的请求次数
		responseTimes     map[roundedMillisecond]int64 // 按优化后的响应时间记录成功请求次数
		failuresTimes     map[string]int64             // 记录不同错误的次数
		startTime         time.Time                    // 第一次收到请求的时间
		lastRequestTime   time.Time                    // 最后一次收到请求的时间
		interval          time.Duration                // 统计时间间隔，影响 CurrentQPS()
		lock              sync.RWMutex
	}

	// QueryResult 查询事件结果
	QueryResult struct {
		Name     string
		Duration time.Duration
		Error    error
	}

	// StatsReport 输出报告
	StatsReport struct {
		Name           string           `json:"name"`
		Requests       int64            `json:"requests"`
		Failures       int64            `json:"failures"`
		Min            int64            `json:"min"`
		Max            int64            `json:"max"`
		Median         int64            `json:"median"`
		Average        int64            `json:"average"`
		QPS            int64            `json:"qps"`
		Distributions  map[string]int64 `json:"distributions"`
		FailureDetails map[string]int64 `json:"failure_details"`
		FullHistory    bool             `json:"full_history"`
	}
)

func newStatsEntry(n string) *statsEntry {
	return &statsEntry{
		name:          n,
		trend:         map[int64]int64{},
		failuresTrend: map[int64]int64{},
		responseTimes: map[roundedMillisecond]int64{},
		failuresTimes: map[string]int64{},
		interval:      time.Second * 12,
	}
}

func (s *statsEntry) logSuccess(t time.Duration) {
	s.lock.Lock()
	defer s.lock.Unlock()

	atomic.AddInt64(&s.numRequests, 1)

	now := time.Now()
	if s.lastRequestTime.IsZero() {
		s.minResponseTime = t
		s.startTime = now
	}
	s.lastRequestTime = now

	if t < s.minResponseTime {
		s.minResponseTime = t
	}
	if t > s.maxResponseTime {
		s.maxResponseTime = t
	}
	s.totalResponseTime += t

	sec := now.Unix()
	if _, ok := s.trend[sec]; ok {
		s.trend[sec]++
	} else {
		s.trend[sec] = 1
	}

	rm := timeDurationToRoudedMillisecond(t)
	if _, ok := s.responseTimes[rm]; ok {
		s.responseTimes[rm]++
	} else {
		s.responseTimes[rm] = 1
	}
}

func (s *statsEntry) logFailure(e error) {
	s.lock.Lock()
	defer s.lock.Unlock()

	sec := time.Now().Unix()
	atomic.AddInt64(&s.numFailures, 1)
	info := e.Error()

	if _, ok := s.failuresTimes[info]; ok {
		s.failuresTimes[info]++
	} else {
		s.failuresTimes[info] = 1
	}

	if _, ok := s.failuresTrend[sec]; ok {
		s.failuresTrend[sec]++
	} else {
		s.failuresTrend[sec] = 1
	}
}

// TotalQPS 获取总的QPS
func (s *statsEntry) totalQPS() float64 {
	return float64(s.numRequests) / s.lastRequestTime.Sub(s.startTime).Seconds()
}

// CurrentQPS 最近12秒的QPS
func (s *statsEntry) currentQPS() float64 {
	s.lock.RLock()
	defer s.lock.RUnlock()

	if s.lastRequestTime.IsZero() {
		return 0
	}
	end := s.lastRequestTime.Unix()
	start := s.lastRequestTime.Add(-s.interval).Unix()
	var total int64

	for k, v := range s.trend {
		if k >= start && k <= end {
			total += v
		}
	}
	return float64(total) / float64(s.interval/time.Second)
}

// Percentile 获取x%的响应时间
func (s *statsEntry) percentile(f float64) time.Duration {
	s.lock.RLock()
	defer s.lock.RUnlock()

	if f <= 0.0 {
		return s.minResponseTime
	}

	if f >= 1.0 {
		return s.maxResponseTime
	}

	hint := int64(float64(s.numRequests)*f + .5)
	if hint == s.numRequests {
		return s.maxResponseTime
	}

	times := []int{}
	for k := range s.responseTimes {
		times = append(times, int(k))
	}

	// sort.Slice(times, func(i, j int) bool { return times[i] < times[j] })
	sort.Ints(times)

	for _, val := range times {
		counts := s.responseTimes[roundedMillisecond(val)]
		hint -= counts
		if hint <= 0 {
			return roundedMillisecondToDuration(roundedMillisecond(val))
		}
	}
	return ZeroDuration // occur error
}

// Min 最快响应时间
func (s *statsEntry) min() time.Duration {
	s.lock.RLock()
	defer s.lock.RUnlock()
	return s.minResponseTime
}

// Max 最慢响应时间
func (s *statsEntry) max() time.Duration {
	s.lock.RLock()
	defer s.lock.RUnlock()
	return s.maxResponseTime
}

// Average 平均响应时间
func (s *statsEntry) average() time.Duration {
	s.lock.RLock()
	defer s.lock.RUnlock()
	if s.numRequests == 0 {
		return ZeroDuration
	}
	return time.Duration(int64(s.totalResponseTime) / int64(s.numRequests))
}

// Median 响应时间中位数
func (s *statsEntry) median() time.Duration {
	return s.percentile(.5)
}

// FailRation 错误率
func (s *statsEntry) failRation() float64 {
	s.lock.RLock()
	defer s.lock.RUnlock()
	return float64(s.numFailures) / float64(s.numRequests+s.numFailures)
}

// Report 打印统计结果
func (s *statsEntry) report(full bool) *StatsReport {
	r := &StatsReport{
		Name:           s.name,
		Requests:       atomic.LoadInt64(&s.numRequests),
		Failures:       atomic.LoadInt64(&s.numFailures),
		Min:            timeDurationToMillsecond(s.min()),
		Max:            timeDurationToMillsecond(s.max()),
		Median:         timeDurationToMillsecond(s.median()),
		Average:        timeDurationToMillsecond(s.average()),
		Distributions:  map[string]int64{},
		FailureDetails: s.failuresTimes,
		FullHistory:    full,
	}

	if full {
		r.QPS = int64(s.totalQPS())
	} else {
		r.QPS = int64(s.currentQPS())
	}

	for _, percent := range timeDistributions {
		r.Distributions[strconv.FormatFloat(percent, 'f', 2, 64)] = timeDurationToMillsecond(s.percentile(percent))
	}
	return r
}
<commit_msg>减少加锁频次<commit_after>package ultron

import (
	"sort"
	"strconv"
	"sync"
	"sync/atomic"
	"time"
)

var timeDistributions = [10]float64{0.5, 0.6, 0.7, 0.8, 0.9, 0.95, 0.97, 0.98, 0.99, 1.0}

type (
	roundedMillisecond int64

	statsEntry struct {
		name              string                       // 统计对象名称
		numRequests       int64                        // 成功请求次数
		numFailures       int64                        // 失败次数
		totalResponseTime time.Duration                // 成功请求的response time总和，基于原始的响应时间
		minResponseTime   time.Duration                // 最快的响应时间
		maxResponseTime   time.Duration                // 最慢的响应时间
		trend             map[int64]int64              // 按时间轴（秒级）记录成功请求次数
		failuresTrend     map[int64]int64              // 按时间轴（妙计）记录错误的请求次数
		responseTimes     map[roundedMillisecond]int64 // 按优化后的响应时间记录成功请求次数
		failuresTimes     map[string]int64             // 记录不同错误的次数
		startTime         time.Time                    // 第一次收到请求的时间
		lastRequestTime   time.Time                    // 最后一次收到请求的时间
		interval          time.Duration                // 统计时间间隔，影响 CurrentQPS()
		lock              sync.RWMutex
	}

	// QueryResult 查询事件结果
	QueryResult struct {
		Name     string
		Duration time.Duration
		Error    error
	}

	// StatsReport 输出报告
	StatsReport struct {
		Name           string           `json:"name"`
		Requests       int64            `json:"requests"`
		Failures       int64            `json:"failures"`
		Min            int64            `json:"min"`
		Max            int64            `json:"max"`
		Median         int64            `json:"median"`
		Average        int64            `json:"average"`
		QPS            int64            `json:"qps"`
		Distributions  map[string]int64 `json:"distributions"`
		FailureDetails map[string]int64 `json:"failure_details"`
		FullHistory    bool             `json:"full_history"`
	}
)

func newStatsEntry(n string) *statsEntry {
	return &statsEntry{
		name:          n,
		trend:         map[int64]int64{},
		failuresTrend: map[int64]int64{},
		responseTimes: map[roundedMillisecond]int64{},
		failuresTimes: map[string]int64{},
		interval:      time.Second * 12,
	}
}

func (s *statsEntry) logSuccess(t time.Duration) {
	s.lock.Lock()
	defer s.lock.Unlock()

	atomic.AddInt64(&s.numRequests, 1)

	now := time.Now()
	if s.lastRequestTime.IsZero() {
		s.minResponseTime = t
		s.startTime = now
	}
	s.lastRequestTime = now

	if t < s.minResponseTime {
		s.minResponseTime = t
	}
	if t > s.maxResponseTime {
		s.maxResponseTime = t
	}
	s.totalResponseTime += t

	sec := now.Unix()
	s.trend[sec]++

	rm := timeDurationToRoudedMillisecond(t)
	s.responseTimes[rm]++
}

func (s *statsEntry) logFailure(e error) {
	s.lock.Lock()
	defer s.lock.Unlock()

	sec := time.Now().Unix()
	atomic.AddInt64(&s.numFailures, 1)
	info := e.Error()

	s.failuresTimes[info]++
	s.failuresTrend[sec]++
}

// TotalQPS 获取总的QPS
func (s *statsEntry) totalQPS() float64 {
	return float64(s.numRequests) / s.lastRequestTime.Sub(s.startTime).Seconds()
}

// CurrentQPS 最近12秒的QPS
func (s *statsEntry) currentQPS() float64 {
	if s.lastRequestTime.IsZero() {
		return 0
	}
	end := s.lastRequestTime.Unix()
	start := s.lastRequestTime.Add(-s.interval).Unix()
	var total int64

	for k, v := range s.trend {
		if k >= start && k <= end {
			total += v
		}
	}
	return float64(total) / float64(s.interval/time.Second)
}

// Percentile 获取x%的响应时间
func (s *statsEntry) percentile(f float64) time.Duration {
	if f <= 0.0 {
		return s.minResponseTime
	}

	if f >= 1.0 {
		return s.maxResponseTime
	}

	hint := int64(float64(s.numRequests)*f + .5)
	if hint == s.numRequests {
		return s.maxResponseTime
	}

	times := []int{}
	for k := range s.responseTimes {
		times = append(times, int(k))
	}

	// sort.Slice(times, func(i, j int) bool { return times[i] < times[j] })
	sort.Ints(times)

	for _, val := range times {
		counts := s.responseTimes[roundedMillisecond(val)]
		hint -= counts
		if hint <= 0 {
			return roundedMillisecondToDuration(roundedMillisecond(val))
		}
	}
	return ZeroDuration // occur error
}

// Min 最快响应时间
func (s *statsEntry) min() time.Duration {
	return s.minResponseTime
}

// Max 最慢响应时间
func (s *statsEntry) max() time.Duration {
	return s.maxResponseTime
}

// Average 平均响应时间
func (s *statsEntry) average() time.Duration {
	if s.numRequests == 0 {
		return ZeroDuration
	}
	return time.Duration(int64(s.totalResponseTime) / int64(s.numRequests))
}

// Median 响应时间中位数
func (s *statsEntry) median() time.Duration {
	return s.percentile(.5)
}

// FailRation 错误率
func (s *statsEntry) failRation() float64 {
	return float64(s.numFailures) / float64(s.numRequests+s.numFailures)
}

// Report 打印统计结果
func (s *statsEntry) report(full bool) *StatsReport {
	s.lock.RLock()
	defer s.lock.RUnlock()

	r := &StatsReport{
		Name:           s.name,
		Requests:       atomic.LoadInt64(&s.numRequests),
		Failures:       atomic.LoadInt64(&s.numFailures),
		Min:            timeDurationToMillsecond(s.min()),
		Max:            timeDurationToMillsecond(s.max()),
		Median:         timeDurationToMillsecond(s.median()),
		Average:        timeDurationToMillsecond(s.average()),
		Distributions:  map[string]int64{},
		FailureDetails: s.failuresTimes,
		FullHistory:    full,
	}

	if full {
		r.QPS = int64(s.totalQPS())
	} else {
		r.QPS = int64(s.currentQPS())
	}

	for _, percent := range timeDistributions {
		r.Distributions[strconv.FormatFloat(percent, 'f', 2, 64)] = timeDurationToMillsecond(s.percentile(percent))
	}
	return r
}
<|endoftext|>


<commit_before>package parser

import (
	"fmt"
)

type Parser func([]byte) (interface{}, error)

var Parsers = map[string]Parser{
	"csv":  Csv,
	"html": Html,
	"json": Json,
	"mime": Mime,
	"xml":  Xml,
	"yaml": Yaml,
}

var preference = []string{
	"json",
	"xml",
	"yaml",
	"csv",
	"html",
	"mime",
}

func Parse(input []byte, format string) (interface{}, string, error) {
	if format == "auto" {
		for _, name := range preference {
			parsed, err := Parsers[name](input)

			if err == nil {
				return parsed, name, err
			}
		}

		return nil, "", fmt.Errorf("Input format could not be identified")
	}

	parser, ok := Parsers[format]

	if !ok {
		return nil, "", fmt.Errorf("Unknown format: %s", format)
	}

	output, err := parser(input)

	return output, format, err
}
<commit_msg>Use a better error message for failure to find a parser<commit_after>package parser

import (
	"fmt"
)

type Parser func([]byte) (interface{}, error)

var Parsers = map[string]Parser{
	"csv":  Csv,
	"html": Html,
	"json": Json,
	"mime": Mime,
	"xml":  Xml,
	"yaml": Yaml,
}

var preference = []string{
	"json",
	"xml",
	"yaml",
	"csv",
	"html",
	"mime",
}

func Parse(input []byte, format string) (interface{}, string, error) {
	if format == "auto" {
		for _, name := range preference {
			parsed, err := Parsers[name](input)

			if err == nil {
				return parsed, name, err
			}
		}

		return nil, "", fmt.Errorf("Input format could not be identified")
	}

	parser, ok := Parsers[format]

	if !ok {
		return nil, "", fmt.Errorf("No such parser: %s", format)
	}

	output, err := parser(input)

	return output, format, err
}
<|endoftext|>


<commit_before>// Copyright 2017 Pilosa Corp.
//
// 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 pilosa

import (
	"expvar"
	"sort"
	"strings"
	"sync"
	"time"
)

func init() {
	NopStatsClient = &nopStatsClient{}
}

// Expvar global expvar map.
var Expvar = expvar.NewMap("index")

// StatsClient represents a client to a stats server.
type StatsClient interface {
	// Returns a sorted list of tags on the client.
	Tags() []string

	// Returns a new client with additional tags appended.
	WithTags(tags ...string) StatsClient

	// Tracks the number of times something occurs per second.
	Count(name string, value int64)

	// Tracks the number of times something occurs per second with custom tags
	CountWithCustomTags(name string, value int64, tags []string)

	// Sets the value of a metric.
	Gauge(name string, value float64)

	// Tracks statistical distribution of a metric.
	Histogram(name string, value float64)

	// Tracks number of unique elements.
	Set(name string, value string)

	// Tracks timing information for a metric.
	Timing(name string, value time.Duration)
}

// NopStatsClient represents a client that doesn't do anything.
var NopStatsClient StatsClient

type nopStatsClient struct{}

func (c *nopStatsClient) Tags() []string                                              { return nil }
func (c *nopStatsClient) WithTags(tags ...string) StatsClient                         { return c }
func (c *nopStatsClient) Count(name string, value int64)                              {}
func (c *nopStatsClient) CountWithCustomTags(name string, value int64, tags []string) {}
func (c *nopStatsClient) Gauge(name string, value float64)                            {}
func (c *nopStatsClient) Histogram(name string, value float64)                        {}
func (c *nopStatsClient) Set(name string, value string)                               {}
func (c *nopStatsClient) Timing(name string, value time.Duration)                     {}

// ExpvarStatsClient writes stats out to expvars.
type ExpvarStatsClient struct {
	mu   sync.Mutex
	m    *expvar.Map
	tags []string
}

// NewExpvarStatsClient returns a new instance of ExpvarStatsClient.
// This client points at the root of the expvar index map.
func NewExpvarStatsClient() *ExpvarStatsClient {
	return &ExpvarStatsClient{
		m: Expvar,
	}
}

// Tags returns a sorted list of tags on the client.
func (c *ExpvarStatsClient) Tags() []string {
	return nil
}

// WithTags returns a new client with additional tags appended.
func (c *ExpvarStatsClient) WithTags(tags ...string) StatsClient {
	m := &expvar.Map{}
	m.Init()
	c.m.Set(strings.Join(tags, ","), m)

	return &ExpvarStatsClient{
		m:    m,
		tags: UnionStringSlice(c.tags, tags),
	}
}

// Count tracks the number of times something occurs.
func (c *ExpvarStatsClient) Count(name string, value int64) {
	c.m.Add(name, value)
}

func (c *ExpvarStatsClient) CountWithCustomTags(name string, value int64, tags []string) {
	c.m.Add(name, value)
}

// Gauge sets the value of a metric.
func (c *ExpvarStatsClient) Gauge(name string, value float64) {
	var f expvar.Float
	f.Set(value)
	c.m.Set(name, &f)
}

// Histogram tracks statistical distribution of a metric.
// This works the same as guage for this client.
func (c *ExpvarStatsClient) Histogram(name string, value float64) {
	c.Gauge(name, value)
}

// Set tracks number of unique elements.
func (c *ExpvarStatsClient) Set(name string, value string) {
	c.m.Set(name, &expvar.String{})
}

// Timing tracks timing information for a metric.
func (c *ExpvarStatsClient) Timing(name string, value time.Duration) {
	c.mu.Lock()
	d, _ := c.m.Get(name).(time.Duration)
	c.m.Set(name, d+value)
	c.mu.Unlock()
}

// MultiStatsClient joins multiple stats clients together.
type MultiStatsClient []StatsClient

// Tags returns tags from the first client.
func (a MultiStatsClient) Tags() []string {
	if len(a) > 0 {
		return a[0].Tags()
	}
	return nil
}

// WithTags returns a new set of clients with the additional tags.
func (a MultiStatsClient) WithTags(tags ...string) StatsClient {
	other := make(MultiStatsClient, len(a))
	for i := range a {
		other[i] = a[i].WithTags(tags...)
	}
	return other
}

// Count tracks the number of times something occurs per second on all clients.
func (a MultiStatsClient) Count(name string, value int64) {
	for _, c := range a {
		c.Count(name, value)
	}
}

func (a MultiStatsClient) CountWithCustomTags(name string, value int64, tags []string) {
	for _, c := range a {
		c.CountWithCustomTags(name, value, tags)
	}
}

// Gauge sets the value of a metric on all clients.
func (a MultiStatsClient) Gauge(name string, value float64) {
	for _, c := range a {
		c.Gauge(name, value)
	}
}

// Histogram tracks statistical distribution of a metric on all clients.
func (a MultiStatsClient) Histogram(name string, value float64) {
	for _, c := range a {
		c.Histogram(name, value)
	}
}

// Set tracks number of unique elements on all clients.
func (a MultiStatsClient) Set(name string, value string) {
	for _, c := range a {
		c.Set(name, value)
	}
}

// Timing tracks timing information for a metric on all clients.
func (a MultiStatsClient) Timing(name string, value time.Duration) {
	for _, c := range a {
		c.Timing(name, value)
	}
}

// UnionStringSlice returns a sorted set of tags which combine a & b.
func UnionStringSlice(a, b []string) []string {
	// Sort both sets first.
	sort.Strings(a)
	sort.Strings(b)

	// Find size of largest slice.
	n := len(a)
	if len(b) > n {
		n = len(b)
	}

	// Exit if both sets are empty.
	if n == 0 {
		return nil
	}

	// Iterate over both in order and merge.
	other := make([]string, 0, n)
	for len(a) > 0 || len(b) > 0 {
		if len(a) == 0 {
			other, b = append(other, b[0]), b[1:]
		} else if len(b) == 0 {
			other, a = append(other, a[0]), a[1:]
		} else if a[0] < b[0] {
			other, a = append(other, a[0]), a[1:]
		} else if b[0] < a[0] {
			other, b = append(other, b[0]), b[1:]
		} else {
			other, a, b = append(other, a[0]), a[1:], b[1:]
		}
	}
	return other
}
<commit_msg>spelling<commit_after>// Copyright 2017 Pilosa Corp.
//
// 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 pilosa

import (
	"expvar"
	"sort"
	"strings"
	"sync"
	"time"
)

func init() {
	NopStatsClient = &nopStatsClient{}
}

// Expvar global expvar map.
var Expvar = expvar.NewMap("index")

// StatsClient represents a client to a stats server.
type StatsClient interface {
	// Returns a sorted list of tags on the client.
	Tags() []string

	// Returns a new client with additional tags appended.
	WithTags(tags ...string) StatsClient

	// Tracks the number of times something occurs per second.
	Count(name string, value int64)

	// Tracks the number of times something occurs per second with custom tags
	CountWithCustomTags(name string, value int64, tags []string)

	// Sets the value of a metric.
	Gauge(name string, value float64)

	// Tracks statistical distribution of a metric.
	Histogram(name string, value float64)

	// Tracks number of unique elements.
	Set(name string, value string)

	// Tracks timing information for a metric.
	Timing(name string, value time.Duration)
}

// NopStatsClient represents a client that doesn't do anything.
var NopStatsClient StatsClient

type nopStatsClient struct{}

func (c *nopStatsClient) Tags() []string                                              { return nil }
func (c *nopStatsClient) WithTags(tags ...string) StatsClient                         { return c }
func (c *nopStatsClient) Count(name string, value int64)                              {}
func (c *nopStatsClient) CountWithCustomTags(name string, value int64, tags []string) {}
func (c *nopStatsClient) Gauge(name string, value float64)                            {}
func (c *nopStatsClient) Histogram(name string, value float64)                        {}
func (c *nopStatsClient) Set(name string, value string)                               {}
func (c *nopStatsClient) Timing(name string, value time.Duration)                     {}

// ExpvarStatsClient writes stats out to expvars.
type ExpvarStatsClient struct {
	mu   sync.Mutex
	m    *expvar.Map
	tags []string
}

// NewExpvarStatsClient returns a new instance of ExpvarStatsClient.
// This client points at the root of the expvar index map.
func NewExpvarStatsClient() *ExpvarStatsClient {
	return &ExpvarStatsClient{
		m: Expvar,
	}
}

// Tags returns a sorted list of tags on the client.
func (c *ExpvarStatsClient) Tags() []string {
	return nil
}

// WithTags returns a new client with additional tags appended.
func (c *ExpvarStatsClient) WithTags(tags ...string) StatsClient {
	m := &expvar.Map{}
	m.Init()
	c.m.Set(strings.Join(tags, ","), m)

	return &ExpvarStatsClient{
		m:    m,
		tags: UnionStringSlice(c.tags, tags),
	}
}

// Count tracks the number of times something occurs.
func (c *ExpvarStatsClient) Count(name string, value int64) {
	c.m.Add(name, value)
}

func (c *ExpvarStatsClient) CountWithCustomTags(name string, value int64, tags []string) {
	c.m.Add(name, value)
}

// Gauge sets the value of a metric.
func (c *ExpvarStatsClient) Gauge(name string, value float64) {
	var f expvar.Float
	f.Set(value)
	c.m.Set(name, &f)
}

// Histogram tracks statistical distribution of a metric.
// This works the same as gauge for this client.
func (c *ExpvarStatsClient) Histogram(name string, value float64) {
	c.Gauge(name, value)
}

// Set tracks number of unique elements.
func (c *ExpvarStatsClient) Set(name string, value string) {
	c.m.Set(name, &expvar.String{})
}

// Timing tracks timing information for a metric.
func (c *ExpvarStatsClient) Timing(name string, value time.Duration) {
	c.mu.Lock()
	d, _ := c.m.Get(name).(time.Duration)
	c.m.Set(name, d+value)
	c.mu.Unlock()
}

// MultiStatsClient joins multiple stats clients together.
type MultiStatsClient []StatsClient

// Tags returns tags from the first client.
func (a MultiStatsClient) Tags() []string {
	if len(a) > 0 {
		return a[0].Tags()
	}
	return nil
}

// WithTags returns a new set of clients with the additional tags.
func (a MultiStatsClient) WithTags(tags ...string) StatsClient {
	other := make(MultiStatsClient, len(a))
	for i := range a {
		other[i] = a[i].WithTags(tags...)
	}
	return other
}

// Count tracks the number of times something occurs per second on all clients.
func (a MultiStatsClient) Count(name string, value int64) {
	for _, c := range a {
		c.Count(name, value)
	}
}

func (a MultiStatsClient) CountWithCustomTags(name string, value int64, tags []string) {
	for _, c := range a {
		c.CountWithCustomTags(name, value, tags)
	}
}

// Gauge sets the value of a metric on all clients.
func (a MultiStatsClient) Gauge(name string, value float64) {
	for _, c := range a {
		c.Gauge(name, value)
	}
}

// Histogram tracks statistical distribution of a metric on all clients.
func (a MultiStatsClient) Histogram(name string, value float64) {
	for _, c := range a {
		c.Histogram(name, value)
	}
}

// Set tracks number of unique elements on all clients.
func (a MultiStatsClient) Set(name string, value string) {
	for _, c := range a {
		c.Set(name, value)
	}
}

// Timing tracks timing information for a metric on all clients.
func (a MultiStatsClient) Timing(name string, value time.Duration) {
	for _, c := range a {
		c.Timing(name, value)
	}
}

// UnionStringSlice returns a sorted set of tags which combine a & b.
func UnionStringSlice(a, b []string) []string {
	// Sort both sets first.
	sort.Strings(a)
	sort.Strings(b)

	// Find size of largest slice.
	n := len(a)
	if len(b) > n {
		n = len(b)
	}

	// Exit if both sets are empty.
	if n == 0 {
		return nil
	}

	// Iterate over both in order and merge.
	other := make([]string, 0, n)
	for len(a) > 0 || len(b) > 0 {
		if len(a) == 0 {
			other, b = append(other, b[0]), b[1:]
		} else if len(b) == 0 {
			other, a = append(other, a[0]), a[1:]
		} else if a[0] < b[0] {
			other, a = append(other, a[0]), a[1:]
		} else if b[0] < a[0] {
			other, b = append(other, b[0]), b[1:]
		} else {
			other, a, b = append(other, a[0]), a[1:], b[1:]
		}
	}
	return other
}
<|endoftext|>


<commit_before>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of Gauge.

// Gauge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Gauge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Gauge.  If not, see <http://www.gnu.org/licenses/>.

package parser

import (
	"strings"

	"github.com/getgauge/common"
	"github.com/getgauge/gauge/gauge"
	"github.com/getgauge/gauge/logger"
)

func ParseSpecFiles(specFiles []string, conceptDictionary *gauge.ConceptDictionary) ([]*gauge.Specification, []*ParseResult) {
	parseResultsChan := make(chan *ParseResult, len(specFiles))
	specsChan := make(chan *gauge.Specification, len(specFiles))
	var parseResults []*ParseResult
	var specs []*gauge.Specification

	for _, specFile := range specFiles {
		go parseSpec(specFile, conceptDictionary, specsChan, parseResultsChan)
	}
	for _ = range specFiles {
		parseResults = append(parseResults, <-parseResultsChan)
		spec := <-specsChan
		if spec != nil {
			specs = append(specs, spec)
		}
	}
	return specs, parseResults
}

func parseSpec(specFile string, conceptDictionary *gauge.ConceptDictionary, specChannel chan *gauge.Specification, parseResultChan chan *ParseResult) {
	specFileContent, err := common.ReadFileContents(specFile)
	if err != nil {
		specChannel <- nil
		parseResultChan <- &ParseResult{ParseErrors: []*ParseError{&ParseError{Message: err.Error()}}, Ok: false, FileName: specFile}
		return
	}
	spec, parseResult := new(SpecParser).Parse(specFileContent, conceptDictionary, specFile)
	specChannel <- spec
	parseResultChan <- parseResult
}

func ExtractStepValueAndParams(stepText string, hasInlineTable bool) (*gauge.StepValue, error) {
	stepValueWithPlaceHolders, args, err := processStepText(stepText)
	if err != nil {
		return nil, err
	}

	extractedStepValue, _ := extractStepValueAndParameterTypes(stepValueWithPlaceHolders)
	if hasInlineTable {
		extractedStepValue += " " + gauge.ParameterPlaceholder
		args = append(args, string(gauge.TableArg))
	}
	parameterizedStepValue := getParameterizeStepValue(extractedStepValue, args)

	return &gauge.StepValue{args, extractedStepValue, parameterizedStepValue}, nil

}

func CreateStepValue(step *gauge.Step) gauge.StepValue {
	stepValue := gauge.StepValue{StepValue: step.Value}
	args := make([]string, 0)
	for _, arg := range step.Args {
		switch arg.ArgType {
		case gauge.Static, gauge.Dynamic:
			args = append(args, arg.Value)
		case gauge.TableArg:
			args = append(args, "table")
		case gauge.SpecialString, gauge.SpecialTable:
			args = append(args, arg.Name)
		}
	}
	stepValue.Args = args
	stepValue.ParameterizedStepValue = getParameterizeStepValue(stepValue.StepValue, args)
	return stepValue
}

func getParameterizeStepValue(stepValue string, params []string) string {
	for _, param := range params {
		stepValue = strings.Replace(stepValue, gauge.ParameterPlaceholder, "<"+param+">", 1)
	}
	return stepValue
}

func HandleParseResult(results ...*ParseResult) bool {
	var failed = false
	for _, result := range results {
		if !result.Ok {
			for _, err := range result.Errors() {
				logger.Errorf(err)
			}
			failed = true
		}
		if result.Warnings != nil {
			for _, warning := range result.Warnings {
				logger.Warning("%s : %v", result.FileName, warning)
			}
		}
	}
	return failed
}
<commit_msg>Adding filename in ParseError instead of ParseResult #425<commit_after>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of Gauge.

// Gauge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Gauge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Gauge.  If not, see <http://www.gnu.org/licenses/>.

package parser

import (
	"strings"

	"github.com/getgauge/common"
	"github.com/getgauge/gauge/gauge"
	"github.com/getgauge/gauge/logger"
)

func ParseSpecFiles(specFiles []string, conceptDictionary *gauge.ConceptDictionary) ([]*gauge.Specification, []*ParseResult) {
	parseResultsChan := make(chan *ParseResult, len(specFiles))
	specsChan := make(chan *gauge.Specification, len(specFiles))
	var parseResults []*ParseResult
	var specs []*gauge.Specification

	for _, specFile := range specFiles {
		go parseSpec(specFile, conceptDictionary, specsChan, parseResultsChan)
	}
	for _ = range specFiles {
		parseResults = append(parseResults, <-parseResultsChan)
		spec := <-specsChan
		if spec != nil {
			specs = append(specs, spec)
		}
	}
	return specs, parseResults
}

func parseSpec(specFile string, conceptDictionary *gauge.ConceptDictionary, specChannel chan *gauge.Specification, parseResultChan chan *ParseResult) {
	specFileContent, err := common.ReadFileContents(specFile)
	if err != nil {
		specChannel <- nil
		parseResultChan <- &ParseResult{ParseErrors: []*ParseError{&ParseError{FileName: specFile, Message: err.Error()}}, Ok: false}
		return
	}
	spec, parseResult := new(SpecParser).Parse(specFileContent, conceptDictionary, specFile)
	specChannel <- spec
	parseResultChan <- parseResult
}

func ExtractStepValueAndParams(stepText string, hasInlineTable bool) (*gauge.StepValue, error) {
	stepValueWithPlaceHolders, args, err := processStepText(stepText)
	if err != nil {
		return nil, err
	}

	extractedStepValue, _ := extractStepValueAndParameterTypes(stepValueWithPlaceHolders)
	if hasInlineTable {
		extractedStepValue += " " + gauge.ParameterPlaceholder
		args = append(args, string(gauge.TableArg))
	}
	parameterizedStepValue := getParameterizeStepValue(extractedStepValue, args)

	return &gauge.StepValue{args, extractedStepValue, parameterizedStepValue}, nil

}

func CreateStepValue(step *gauge.Step) gauge.StepValue {
	stepValue := gauge.StepValue{StepValue: step.Value}
	args := make([]string, 0)
	for _, arg := range step.Args {
		switch arg.ArgType {
		case gauge.Static, gauge.Dynamic:
			args = append(args, arg.Value)
		case gauge.TableArg:
			args = append(args, "table")
		case gauge.SpecialString, gauge.SpecialTable:
			args = append(args, arg.Name)
		}
	}
	stepValue.Args = args
	stepValue.ParameterizedStepValue = getParameterizeStepValue(stepValue.StepValue, args)
	return stepValue
}

func getParameterizeStepValue(stepValue string, params []string) string {
	for _, param := range params {
		stepValue = strings.Replace(stepValue, gauge.ParameterPlaceholder, "<"+param+">", 1)
	}
	return stepValue
}

func HandleParseResult(results ...*ParseResult) bool {
	var failed = false
	for _, result := range results {
		if !result.Ok {
			for _, err := range result.Errors() {
				logger.Errorf(err)
			}
			failed = true
		}
		if result.Warnings != nil {
			for _, warning := range result.Warnings {
				logger.Warning("%s : %v", result.FileName, warning)
			}
		}
	}
	return failed
}
<|endoftext|>