├── functions ├── host.json ├── azure │ ├── function.json │ ├── Makefile │ └── index.js ├── google │ ├── Makefile │ └── index.js └── lambda │ ├── Makefile │ └── index.js ├── cmd ├── recordersvc │ ├── app.yaml │ └── main.go └── recordercli │ └── main.go ├── .gitignore ├── sieve └── sieve.go ├── gen ├── http │ ├── recorder │ │ ├── client │ │ │ ├── paths.go │ │ │ ├── types.go │ │ │ ├── cli.go │ │ │ ├── client.go │ │ │ └── encode_decode.go │ │ └── server │ │ │ ├── paths.go │ │ │ ├── types.go │ │ │ ├── encode_decode.go │ │ │ └── server.go │ ├── openapi.json │ ├── cli │ │ └── cli.go │ └── openapi.yaml └── recorder │ ├── client.go │ ├── service.go │ └── endpoints.go ├── poster └── poster.go ├── Makefile ├── design └── design.go └── recorder.go /functions/host.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /cmd/recordersvc/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1 3 | 4 | handlers: 5 | - url: /.* 6 | script: _go_app 7 | 8 | -------------------------------------------------------------------------------- /functions/azure/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "authLevel": "anonymous", 5 | "type": "httpTrigger", 6 | "direction": "in", 7 | "name": "req" 8 | }, 9 | { 10 | "type": "http", 11 | "direction": "out", 12 | "name": "res" 13 | } 14 | ], 15 | "disabled": false 16 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | functions/lambda/handler 2 | functions/lambda/handler.zip 3 | functions/lambda/out.txt 4 | cmd/recordercli/recordercli 5 | cmd/recordersvc/recordersvc 6 | functions/google/.gcloudignore 7 | functions/google/function.zip 8 | functions/google/main 9 | functions/google/node_modules/execer 10 | functions/azure/sieve.zip 11 | functions/google/sieve.zip 12 | functions/google/handler.zip 13 | -------------------------------------------------------------------------------- /sieve/sieve.go: -------------------------------------------------------------------------------- 1 | package sieve 2 | 3 | import "time" 4 | 5 | // return list of primes less than N 6 | func Eratosthenes(N int) (primes []int, dur float64) { 7 | start := time.Now() 8 | b := make([]bool, N) 9 | for i := 2; i < N; i++ { 10 | if b[i] == true { 11 | continue 12 | } 13 | primes = append(primes, i) 14 | for k := i * i; k < N; k += i { 15 | b[k] = true 16 | } 17 | } 18 | dur = float64(time.Since(start).Nanoseconds()) / 1000 19 | return 20 | } 21 | -------------------------------------------------------------------------------- /gen/http/recorder/client/paths.go: -------------------------------------------------------------------------------- 1 | // Code generated with goa v2.0.0-wip, DO NOT EDIT. 2 | // 3 | // HTTP request path constructors for the recorder service. 4 | // 5 | // Command: 6 | // $ goa gen github.com/raphael/recorder/design 7 | 8 | package client 9 | 10 | // RecordDataRecorderPath returns the URL path to the recorder service record-data HTTP endpoint. 11 | func RecordDataRecorderPath() string { 12 | return "/data" 13 | } 14 | 15 | // ListRecorderPath returns the URL path to the recorder service list HTTP endpoint. 16 | func ListRecorderPath() string { 17 | return "/data" 18 | } 19 | -------------------------------------------------------------------------------- /gen/http/recorder/server/paths.go: -------------------------------------------------------------------------------- 1 | // Code generated with goa v2.0.0-wip, DO NOT EDIT. 2 | // 3 | // HTTP request path constructors for the recorder service. 4 | // 5 | // Command: 6 | // $ goa gen github.com/raphael/recorder/design 7 | 8 | package server 9 | 10 | // RecordDataRecorderPath returns the URL path to the recorder service record-data HTTP endpoint. 11 | func RecordDataRecorderPath() string { 12 | return "/data" 13 | } 14 | 15 | // ListRecorderPath returns the URL path to the recorder service list HTTP endpoint. 16 | func ListRecorderPath() string { 17 | return "/data" 18 | } 19 | -------------------------------------------------------------------------------- /functions/google/Makefile: -------------------------------------------------------------------------------- 1 | FUNCTION = sieve 2 | REGION = us-central1 3 | PROJECT = optima-tve 4 | MEMORY ?= 512 5 | 6 | build: 7 | @zip handler.zip ./index.js 8 | 9 | deploy: build 10 | @gcloud beta functions deploy $(FUNCTION) \ 11 | --memory=$(MEMORY) \ 12 | --region=$(REGION) \ 13 | --source=. \ 14 | --trigger-http \ 15 | --project=$(PROJECT) 16 | 17 | delete: 18 | @gcloud beta functions delete $(FUNCTION) --project=$(PROJECT) 19 | 20 | run: 21 | http -v POST https://$(REGION)-$(PROJECT).cloudfunctions.net/$(FUNCTION)?n=10 22 | 23 | benchmark: 24 | @i=1 ; while [[ $$i -le 100 ]] ; do \ 25 | http POST https://$(REGION)-$(PROJECT).cloudfunctions.net/$(FUNCTION)?n=10000000 >/dev/null && \ 26 | echo google: $$i% && \ 27 | ((i = i + 1)) ; \ 28 | done -------------------------------------------------------------------------------- /poster/poster.go: -------------------------------------------------------------------------------- 1 | package poster 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/raphael/recorder/gen/http/recorder/client" 8 | recorder "github.com/raphael/recorder/gen/recorder" 9 | "goa.design/goa" 10 | goahttp "goa.design/goa/http" 11 | ) 12 | 13 | // A Poster posts datapoints to the recorder service. 14 | type Poster goa.Endpoint 15 | 16 | // New creates a datapoint poster. 17 | func New(host string) Poster { 18 | c := client.NewClient("https", host, http.DefaultClient, 19 | goahttp.RequestEncoder, goahttp.ResponseDecoder, false) 20 | return Poster(c.RecordData()) 21 | } 22 | 23 | // Post posts a new datapoint. 24 | func (p Poster) Post(ctx context.Context, point *recorder.Datapoint) error { 25 | _, err := p(ctx, point) 26 | return err 27 | } 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOOS=linux 2 | PROJECT=optima-tve 3 | 4 | build: 5 | @cd cmd/recordersvc && go build -o recordersvc 6 | 7 | deploy: 8 | @cd cmd/recordersvc && gcloud app deploy --project $(PROJECT) 9 | 10 | deploy_all: deploy 11 | make -C functions/lambda deploy 12 | make -C functions/google deploy 13 | make -C functions/azure deploy 14 | 15 | delete: 16 | @cd cmd/recordersvc && gcloud app versions delete --project $(PROJECT) 17 | make -C functions/lambda delete 18 | make -C functions/google delete 19 | make -C functions/azure delete 20 | 21 | init: 22 | make -C functions/lambda init 23 | make -C functions/google init 24 | make -C functions/azure init 25 | 26 | benchmark: 27 | make -C functions/lambda benchmark 28 | make -C functions/google benchmark 29 | make -C functions/azure benchmark -------------------------------------------------------------------------------- /functions/azure/Makefile: -------------------------------------------------------------------------------- 1 | GOOS=linux 2 | GROUP=functions 3 | FUNCTION=benchmarksieve 4 | REGION=westus2 5 | STORAGE=benchmarkserverless 6 | OUT=sieve.zip 7 | 8 | build: 9 | @rm -f $(OUT) 10 | @zip -FS -r $(OUT) ../ -x "*/google/*" "*/lambda/*" "*/Makefile" 11 | 12 | init: 13 | @az group create --name $(GROUP) --location $(REGION) 14 | @az storage account create --name $(STORAGE) --location $(REGION) \ 15 | --resource-group $(GROUP) --sku Standard_LRS 16 | @az functionapp create \ 17 | --resource-group $(GROUP) --consumption-plan-location $(REGION) \ 18 | --name $(FUNCTION) --storage-account $(STORAGE) 19 | 20 | deploy: build 21 | @az functionapp deployment source config-zip -g $(GROUP) \ 22 | -n $(FUNCTION) --src $(OUT) 23 | 24 | delete: 25 | @az group delete --name $(GROUP) 26 | 27 | run: 28 | http -v http://$(FUNCTION).azurewebsites.net/api/azure?n=10 29 | 30 | benchmark: 31 | @i=1 ; while [[ $$i -le 100 ]] ; do \ 32 | http -v http://$(FUNCTION).azurewebsites.net/api/azure?n=10000000 >/dev/null && \ 33 | echo azure: $$i% && \ 34 | ((i = i + 1)) ; \ 35 | done -------------------------------------------------------------------------------- /gen/http/recorder/client/types.go: -------------------------------------------------------------------------------- 1 | // Code generated with goa v2.0.0-wip, DO NOT EDIT. 2 | // 3 | // recorder HTTP client types 4 | // 5 | // Command: 6 | // $ goa gen github.com/raphael/recorder/design 7 | 8 | package client 9 | 10 | import ( 11 | recordersvc "github.com/raphael/recorder/gen/recorder" 12 | ) 13 | 14 | // RecordDataRequestBody is the type of the "recorder" service "record-data" 15 | // endpoint HTTP request body. 16 | type RecordDataRequestBody struct { 17 | // Service that created datapoint. 18 | Service string `form:"service" json:"service" xml:"service"` 19 | // Datapoint value. 20 | Value float64 `form:"value" json:"value" xml:"value"` 21 | // Name is the name of the datapoint. 22 | Name string `form:"name" json:"name" xml:"name"` 23 | } 24 | 25 | // NewRecordDataRequestBody builds the HTTP request body from the payload of 26 | // the "record-data" endpoint of the "recorder" service. 27 | func NewRecordDataRequestBody(p *recordersvc.Datapoint) *RecordDataRequestBody { 28 | body := &RecordDataRequestBody{ 29 | Service: p.Service, 30 | Value: p.Value, 31 | Name: p.Name, 32 | } 33 | return body 34 | } 35 | -------------------------------------------------------------------------------- /functions/lambda/Makefile: -------------------------------------------------------------------------------- 1 | GOOS = linux 2 | FUNCTION = sieve 3 | REGION = us-west-2 4 | MEMORY ?= 512 5 | 6 | build: 7 | @zip handler.zip ./index.js 8 | 9 | init: build 10 | @aws lambda create-function \ 11 | --region $(REGION) \ 12 | --function-name $(FUNCTION) \ 13 | --memory $(MEMORY) \ 14 | --role arn:aws:iam::586789346966:role/dynamo-lambda-prod-role \ 15 | --runtime nodejs8.10 \ 16 | --zip-file fileb://handler.zip \ 17 | --handler .handler 18 | 19 | deploy: build 20 | @aws lambda update-function-code \ 21 | --region $(REGION) \ 22 | --function-name $(FUNCTION) \ 23 | --zip-file fileb://handler.zip 24 | 25 | delete: 26 | @aws lambda delete-function \ 27 | --region $(REGION) \ 28 | --function-name $(FUNCTION) 29 | 30 | run: 31 | @aws lambda invoke \ 32 | --region $(REGION) \ 33 | --function-name $(FUNCTION) \ 34 | --payload 10 \ 35 | out.txt && \ 36 | cat out.txt 37 | 38 | benchmark: 39 | @i=1 ; while [[ $$i -le 100 ]] ; do \ 40 | aws lambda invoke \ 41 | --region $(REGION) \ 42 | --function-name $(FUNCTION) \ 43 | --payload 10000000 \ 44 | out.txt > /dev/null && \ 45 | echo lambda: $$i% && \ 46 | ((i = i + 1)) ; \ 47 | done -------------------------------------------------------------------------------- /gen/recorder/client.go: -------------------------------------------------------------------------------- 1 | // Code generated with goa v2.0.0-wip, DO NOT EDIT. 2 | // 3 | // recorder client 4 | // 5 | // Command: 6 | // $ goa gen github.com/raphael/recorder/design 7 | 8 | package recordersvc 9 | 10 | import ( 11 | "context" 12 | 13 | goa "goa.design/goa" 14 | ) 15 | 16 | // Client is the "recorder" service client. 17 | type Client struct { 18 | RecordDataEndpoint goa.Endpoint 19 | ListEndpoint goa.Endpoint 20 | } 21 | 22 | // NewClient initializes a "recorder" service client given the endpoints. 23 | func NewClient(recordData, list goa.Endpoint) *Client { 24 | return &Client{ 25 | RecordDataEndpoint: recordData, 26 | ListEndpoint: list, 27 | } 28 | } 29 | 30 | // RecordData calls the "record-data" endpoint of the "recorder" service. 31 | func (c *Client) RecordData(ctx context.Context, p *Datapoint) (err error) { 32 | _, err = c.RecordDataEndpoint(ctx, p) 33 | return 34 | } 35 | 36 | // List calls the "list" endpoint of the "recorder" service. 37 | func (c *Client) List(ctx context.Context, p *Series) (res []float64, err error) { 38 | var ires interface{} 39 | ires, err = c.ListEndpoint(ctx, p) 40 | if err != nil { 41 | return 42 | } 43 | return ires.([]float64), nil 44 | } 45 | -------------------------------------------------------------------------------- /gen/recorder/service.go: -------------------------------------------------------------------------------- 1 | // Code generated with goa v2.0.0-wip, DO NOT EDIT. 2 | // 3 | // recorder service 4 | // 5 | // Command: 6 | // $ goa gen github.com/raphael/recorder/design 7 | 8 | package recordersvc 9 | 10 | import ( 11 | "context" 12 | ) 13 | 14 | // Service is the recorder service interface. 15 | type Service interface { 16 | // RecordData creates a new datapoint. 17 | RecordData(context.Context, *Datapoint) error 18 | // List lists all recorded datapoints. 19 | List(context.Context, *Series) ([]float64, error) 20 | } 21 | 22 | // ServiceName is the name of the service as defined in the design. This is the 23 | // same value that is set in the endpoint request contexts under the ServiceKey 24 | // key. 25 | const ServiceName = "recorder" 26 | 27 | // MethodNames lists the service method names as defined in the design. These 28 | // are the same values that are set in the endpoint request contexts under the 29 | // MethodKey key. 30 | var MethodNames = [2]string{"record-data", "list"} 31 | 32 | // Datapoint is the payload type of the recorder service record-data method. 33 | type Datapoint struct { 34 | // Service that created datapoint. 35 | Service string 36 | // Datapoint value. 37 | Value float64 38 | // Name is the name of the datapoint. 39 | Name string 40 | } 41 | 42 | // Series is the payload type of the recorder service list method. 43 | type Series struct { 44 | // Service that created datapoint. 45 | Service string 46 | // Name is the name of the datapoint. 47 | Name string 48 | } 49 | -------------------------------------------------------------------------------- /gen/recorder/endpoints.go: -------------------------------------------------------------------------------- 1 | // Code generated with goa v2.0.0-wip, DO NOT EDIT. 2 | // 3 | // recorder endpoints 4 | // 5 | // Command: 6 | // $ goa gen github.com/raphael/recorder/design 7 | 8 | package recordersvc 9 | 10 | import ( 11 | "context" 12 | 13 | goa "goa.design/goa" 14 | ) 15 | 16 | // Endpoints wraps the "recorder" service endpoints. 17 | type Endpoints struct { 18 | RecordData goa.Endpoint 19 | List goa.Endpoint 20 | } 21 | 22 | // NewEndpoints wraps the methods of the "recorder" service with endpoints. 23 | func NewEndpoints(s Service) *Endpoints { 24 | return &Endpoints{ 25 | RecordData: NewRecordDataEndpoint(s), 26 | List: NewListEndpoint(s), 27 | } 28 | } 29 | 30 | // Use applies the given middleware to all the "recorder" service endpoints. 31 | func (e *Endpoints) Use(m func(goa.Endpoint) goa.Endpoint) { 32 | e.RecordData = m(e.RecordData) 33 | e.List = m(e.List) 34 | } 35 | 36 | // NewRecordDataEndpoint returns an endpoint function that calls the method 37 | // "record-data" of service "recorder". 38 | func NewRecordDataEndpoint(s Service) goa.Endpoint { 39 | return func(ctx context.Context, req interface{}) (interface{}, error) { 40 | p := req.(*Datapoint) 41 | return nil, s.RecordData(ctx, p) 42 | } 43 | } 44 | 45 | // NewListEndpoint returns an endpoint function that calls the method "list" of 46 | // service "recorder". 47 | func NewListEndpoint(s Service) goa.Endpoint { 48 | return func(ctx context.Context, req interface{}) (interface{}, error) { 49 | p := req.(*Series) 50 | return s.List(ctx, p) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /gen/http/openapi.json: -------------------------------------------------------------------------------- 1 | {"swagger":"2.0","info":{"title":"Recorder API","description":"Recorder records arbitrary data together with the recording timestamp","version":""},"host":"localhost:8080","paths":{"/data":{"get":{"tags":["recorder"],"summary":"list recorder","description":"List lists all recorded datapoints.","operationId":"recorder#list","parameters":[{"name":"service","in":"query","description":"Service that created datapoint.","required":true,"type":"string"},{"name":"name","in":"query","description":"Name is the name of the datapoint.","required":true,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"type":"array","items":{"type":"number","example":0.573062816327359,"format":"double"}}}},"schemes":["http"]},"post":{"tags":["recorder"],"summary":"record-data recorder","description":"RecordData creates a new datapoint.","operationId":"recorder#record-data","parameters":[{"name":"RecordDataRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/RecordDataRequestBody"}}],"responses":{"200":{"description":"OK response."}},"schemes":["http"]}}},"definitions":{"RecordDataRequestBody":{"title":"RecordDataRequestBody","type":"object","properties":{"name":{"type":"string","description":"Name is the name of the datapoint.","example":"duration"},"service":{"type":"string","description":"Service that created datapoint.","example":"lambda"},"value":{"type":"number","description":"Datapoint value.","example":0.7022016202939775,"format":"double"}},"example":{"name":"duration","service":"lambda","value":0.3403727736572397},"required":["service","name","value"]}}} -------------------------------------------------------------------------------- /gen/http/recorder/client/cli.go: -------------------------------------------------------------------------------- 1 | // Code generated with goa v2.0.0-wip, DO NOT EDIT. 2 | // 3 | // recorder HTTP client CLI support package 4 | // 5 | // Command: 6 | // $ goa gen github.com/raphael/recorder/design 7 | 8 | package client 9 | 10 | import ( 11 | "encoding/json" 12 | "fmt" 13 | 14 | recordersvc "github.com/raphael/recorder/gen/recorder" 15 | ) 16 | 17 | // BuildRecordDataPayload builds the payload for the recorder record-data 18 | // endpoint from CLI flags. 19 | func BuildRecordDataPayload(recorderRecordDataBody string) (*recordersvc.Datapoint, error) { 20 | var err error 21 | var body RecordDataRequestBody 22 | { 23 | err = json.Unmarshal([]byte(recorderRecordDataBody), &body) 24 | if err != nil { 25 | return nil, fmt.Errorf("invalid JSON for body, example of valid JSON:\n%s", "'{\n \"name\": \"duration\",\n \"service\": \"lambda\",\n \"value\": 0.06173619203715241\n }'") 26 | } 27 | } 28 | if err != nil { 29 | return nil, err 30 | } 31 | v := &recordersvc.Datapoint{ 32 | Service: body.Service, 33 | Value: body.Value, 34 | Name: body.Name, 35 | } 36 | return v, nil 37 | } 38 | 39 | // BuildListPayload builds the payload for the recorder list endpoint from CLI 40 | // flags. 41 | func BuildListPayload(recorderListService string, recorderListName string) (*recordersvc.Series, error) { 42 | var service string 43 | { 44 | service = recorderListService 45 | } 46 | var name string 47 | { 48 | name = recorderListName 49 | } 50 | payload := &recordersvc.Series{ 51 | Service: service, 52 | Name: name, 53 | } 54 | return payload, nil 55 | } 56 | -------------------------------------------------------------------------------- /design/design.go: -------------------------------------------------------------------------------- 1 | package design 2 | 3 | import ( 4 | . "goa.design/goa/http/design" 5 | . "goa.design/goa/http/dsl" 6 | ) 7 | 8 | var _ = API("recorder", func() { 9 | Title("Recorder API") 10 | Description("Recorder records arbitrary data together with the recording timestamp") 11 | }) 12 | 13 | var _ = Service("recorder", func() { 14 | Method("record-data", func() { 15 | Description("RecordData creates a new datapoint.") 16 | Payload(Datapoint) 17 | Result(Empty) 18 | HTTP(func() { 19 | POST("/data") 20 | Response(StatusOK) 21 | }) 22 | }) 23 | Method("list", func() { 24 | Description("List lists all recorded datapoints.") 25 | Payload(Series) 26 | Result(ArrayOf(Float64)) 27 | HTTP(func() { 28 | GET("/data") 29 | Param("service") 30 | Param("name") 31 | Response(StatusOK) 32 | }) 33 | }) 34 | }) 35 | 36 | var Series = Type("Series", func() { 37 | Description("Series represent a time series.") 38 | Attribute("service", String, "Service that created datapoint.", func() { 39 | Example("lambda") 40 | }) 41 | Attribute("name", String, "Name is the name of the datapoint.", func() { 42 | Example("duration") 43 | }) 44 | Required("service", "name") 45 | }) 46 | 47 | var Datapoint = Type("Datapoint", func() { 48 | Description("Datapoint describes a single recording datapoint.") 49 | Attribute("service", String, "Service that created datapoint.", func() { 50 | Example("lambda") 51 | }) 52 | Attribute("value", Float64, "Datapoint value.") 53 | Attribute("name", String, "Name is the name of the datapoint.", func() { 54 | Example("duration") 55 | }) 56 | Required("service", "name", "value") 57 | }) 58 | -------------------------------------------------------------------------------- /functions/lambda/index.js: -------------------------------------------------------------------------------- 1 | const US_PER_SEC = 1e6; 2 | const NS_PER_US = 1e3; 3 | const https = require('https'); 4 | 5 | function eratosthenes(n) { 6 | var sieve = []; 7 | var output = []; 8 | 9 | for (var i = 0; i < n; i++) { 10 | sieve.push(i); 11 | } 12 | var limit = Math.sqrt(n); 13 | for (var i = 2; i <= limit; i++) { 14 | if (sieve[i]) { 15 | for (var j = i * i; j < n; j += i) { 16 | sieve[j] = false; 17 | } 18 | } 19 | } 20 | var arr = []; 21 | for (var k = 2; k < sieve.length; k++) { 22 | if (sieve[k]) { 23 | arr.push(sieve[k]); 24 | } 25 | } 26 | return arr; 27 | }; 28 | 29 | exports.handler = function(event, context, callback) { 30 | var n = event 31 | if (n) { 32 | var start = process.hrtime() 33 | eratosthenes(n); 34 | var elapsed = process.hrtime(start); 35 | var dur = elapsed[0] * US_PER_SEC + elapsed[1] / NS_PER_US; 36 | 37 | var options = { 38 | host: "optima-tve.appspot.com", 39 | port: 443, 40 | path: '/data', 41 | method: 'POST' 42 | }; 43 | var req = https.request(options, function (res) { 44 | var body = ""; 45 | res.on('data', function (b) { 46 | body += b; 47 | }) 48 | res.on('end', function() { 49 | status = res.statusCode; 50 | if (status != 200) { 51 | callback(body); 52 | context.done(); 53 | return; 54 | } 55 | 56 | callback(null, dur); 57 | context.done(); 58 | }); 59 | }); 60 | req.write("{" + 61 | '"service": "lambda",' + 62 | '"name": "sieve-' + n + '",' + 63 | '"value":' + dur + 64 | "}") 65 | req.end() 66 | } 67 | else { 68 | callback("Please pass a value for N in the request body") 69 | context.done(); 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /functions/google/index.js: -------------------------------------------------------------------------------- 1 | const US_PER_SEC = 1e6; 2 | const NS_PER_US = 1e3; 3 | const https = require('https'); 4 | function eratosthenes(n) { 5 | var sieve = []; 6 | var output = []; 7 | 8 | for (var i = 0; i < n; i++) { 9 | sieve.push(i); 10 | } 11 | var limit = Math.sqrt(n); 12 | for (var i = 2; i <= limit; i++) { 13 | if (sieve[i]) { 14 | for (var j = i * i; j < n; j += i) { 15 | sieve[j] = false; 16 | } 17 | } 18 | } 19 | var arr = []; 20 | for (var k = 2; k < sieve.length; k++) { 21 | if (sieve[k]) { 22 | arr.push(sieve[k]); 23 | } 24 | } 25 | return arr; 26 | }; 27 | 28 | exports.sieve = function (req, res) { 29 | var n = req.query.n || (req.body && req.body.n); 30 | if (n) { 31 | var start = process.hrtime() 32 | eratosthenes(n); 33 | var elapsed = process.hrtime(start); 34 | var dur = elapsed[0] * US_PER_SEC + elapsed[1] / NS_PER_US; 35 | 36 | var options = { 37 | host: "optima-tve.appspot.com", 38 | port: 443, 39 | path: '/data', 40 | method: 'POST' 41 | }; 42 | var rec = https.request(options, function (resp) { 43 | var body = ""; 44 | resp.on('data', function (b) { 45 | body += b; 46 | }) 47 | resp.on('end', function() { 48 | status = resp.statusCode; 49 | if (status != 200) { 50 | res.status(500).json({"error": body}).end(); 51 | return 52 | } 53 | 54 | res.status(200).json(dur).end() 55 | }); 56 | }); 57 | rec.write("{" + 58 | '"service": "google",' + 59 | '"name": "sieve-' + n + '",' + 60 | '"value":' + dur + 61 | "}") 62 | rec.on("error", function() { 63 | res.status(500).send("cannot make request to recorder"); 64 | return 65 | }); 66 | rec.end() 67 | } 68 | else { 69 | res.status(400).send("Please pass a value for N in the request body"); 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /functions/azure/index.js: -------------------------------------------------------------------------------- 1 | const US_PER_SEC = 1e6; 2 | const NS_PER_US = 1e3; 3 | const https = require('https'); 4 | function eratosthenes(n) { 5 | var sieve = []; 6 | var output = []; 7 | 8 | for (var i = 0; i < n; i++) { 9 | sieve.push(i); 10 | } 11 | var limit = Math.sqrt(n); 12 | for (var i = 2; i <= limit; i++) { 13 | if (sieve[i]) { 14 | for (var j = i * i; j < n; j += i) { 15 | sieve[j] = false; 16 | } 17 | } 18 | } 19 | var arr = []; 20 | for (var k = 2; k < sieve.length; k++) { 21 | if (sieve[k]) { 22 | arr.push(sieve[k]); 23 | } 24 | } 25 | return arr; 26 | }; 27 | 28 | module.exports = function (context, req) { 29 | var n = req.query.n || (req.body && req.body.n); 30 | if (n) { 31 | var start = process.hrtime() 32 | eratosthenes(n); 33 | var elapsed = process.hrtime(start); 34 | var dur = elapsed[0] * US_PER_SEC + elapsed[1] / NS_PER_US; 35 | 36 | var options = { 37 | host: "optima-tve.appspot.com", 38 | port: 443, 39 | path: '/data', 40 | method: 'POST' 41 | }; 42 | var req = https.request(options, function (res) { 43 | var body = ""; 44 | res.on('data', function (b) { 45 | body += b; 46 | }) 47 | res.on('end', function() { 48 | status = res.statusCode; 49 | if (status != 200) { 50 | context.res = { 51 | status: 500, 52 | body: body + " (status: " + status + ")", 53 | }; 54 | context.done() 55 | return 56 | } 57 | 58 | context.res = { 59 | status: 200, 60 | body: dur, 61 | }; 62 | context.done(); 63 | }); 64 | }); 65 | req.write("{" + 66 | '"service": "azure",' + 67 | '"name": "sieve-' + n + '",' + 68 | '"value":' + dur + 69 | "}") 70 | req.end() 71 | } 72 | else { 73 | context.res = { 74 | status: 400, 75 | body: "Please pass a value for N in the request body" 76 | }; 77 | context.done(); 78 | } 79 | }; 80 | -------------------------------------------------------------------------------- /gen/http/recorder/server/types.go: -------------------------------------------------------------------------------- 1 | // Code generated with goa v2.0.0-wip, DO NOT EDIT. 2 | // 3 | // recorder HTTP server types 4 | // 5 | // Command: 6 | // $ goa gen github.com/raphael/recorder/design 7 | 8 | package server 9 | 10 | import ( 11 | recordersvc "github.com/raphael/recorder/gen/recorder" 12 | goa "goa.design/goa" 13 | ) 14 | 15 | // RecordDataRequestBody is the type of the "recorder" service "record-data" 16 | // endpoint HTTP request body. 17 | type RecordDataRequestBody struct { 18 | // Service that created datapoint. 19 | Service *string `form:"service,omitempty" json:"service,omitempty" xml:"service,omitempty"` 20 | // Datapoint value. 21 | Value *float64 `form:"value,omitempty" json:"value,omitempty" xml:"value,omitempty"` 22 | // Name is the name of the datapoint. 23 | Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` 24 | } 25 | 26 | // NewRecordDataDatapoint builds a recorder service record-data endpoint 27 | // payload. 28 | func NewRecordDataDatapoint(body *RecordDataRequestBody) *recordersvc.Datapoint { 29 | v := &recordersvc.Datapoint{ 30 | Service: *body.Service, 31 | Value: *body.Value, 32 | Name: *body.Name, 33 | } 34 | return v 35 | } 36 | 37 | // NewListSeries builds a recorder service list endpoint payload. 38 | func NewListSeries(service string, name string) *recordersvc.Series { 39 | return &recordersvc.Series{ 40 | Service: service, 41 | Name: name, 42 | } 43 | } 44 | 45 | // Validate runs the validations defined on RecordDataRequestBody 46 | func (body *RecordDataRequestBody) Validate() (err error) { 47 | if body.Service == nil { 48 | err = goa.MergeErrors(err, goa.MissingFieldError("service", "body")) 49 | } 50 | if body.Name == nil { 51 | err = goa.MergeErrors(err, goa.MissingFieldError("name", "body")) 52 | } 53 | if body.Value == nil { 54 | err = goa.MergeErrors(err, goa.MissingFieldError("value", "body")) 55 | } 56 | return 57 | } 58 | -------------------------------------------------------------------------------- /gen/http/recorder/server/encode_decode.go: -------------------------------------------------------------------------------- 1 | // Code generated with goa v2.0.0-wip, DO NOT EDIT. 2 | // 3 | // recorder HTTP server encoders and decoders 4 | // 5 | // Command: 6 | // $ goa gen github.com/raphael/recorder/design 7 | 8 | package server 9 | 10 | import ( 11 | "context" 12 | "io" 13 | "net/http" 14 | 15 | goa "goa.design/goa" 16 | goahttp "goa.design/goa/http" 17 | ) 18 | 19 | // EncodeRecordDataResponse returns an encoder for responses returned by the 20 | // recorder record-data endpoint. 21 | func EncodeRecordDataResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, interface{}) error { 22 | return func(ctx context.Context, w http.ResponseWriter, v interface{}) error { 23 | w.WriteHeader(http.StatusOK) 24 | return nil 25 | } 26 | } 27 | 28 | // DecodeRecordDataRequest returns a decoder for requests sent to the recorder 29 | // record-data endpoint. 30 | func DecodeRecordDataRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (interface{}, error) { 31 | return func(r *http.Request) (interface{}, error) { 32 | var ( 33 | body RecordDataRequestBody 34 | err error 35 | ) 36 | err = decoder(r).Decode(&body) 37 | if err != nil { 38 | if err == io.EOF { 39 | return nil, goa.MissingPayloadError() 40 | } 41 | return nil, goa.DecodePayloadError(err.Error()) 42 | } 43 | err = body.Validate() 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | return NewRecordDataDatapoint(&body), nil 49 | } 50 | } 51 | 52 | // EncodeListResponse returns an encoder for responses returned by the recorder 53 | // list endpoint. 54 | func EncodeListResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, interface{}) error { 55 | return func(ctx context.Context, w http.ResponseWriter, v interface{}) error { 56 | res := v.([]float64) 57 | enc := encoder(ctx, w) 58 | body := res 59 | w.WriteHeader(http.StatusOK) 60 | return enc.Encode(body) 61 | } 62 | } 63 | 64 | // DecodeListRequest returns a decoder for requests sent to the recorder list 65 | // endpoint. 66 | func DecodeListRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (interface{}, error) { 67 | return func(r *http.Request) (interface{}, error) { 68 | var ( 69 | service string 70 | name string 71 | err error 72 | ) 73 | service = r.URL.Query().Get("service") 74 | if service == "" { 75 | err = goa.MergeErrors(err, goa.MissingFieldError("service", "query string")) 76 | } 77 | name = r.URL.Query().Get("name") 78 | if name == "" { 79 | err = goa.MergeErrors(err, goa.MissingFieldError("name", "query string")) 80 | } 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | return NewListSeries(service, name), nil 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /cmd/recordercli/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "strings" 12 | "time" 13 | 14 | "github.com/raphael/recorder/gen/http/cli" 15 | goahttp "goa.design/goa/http" 16 | ) 17 | 18 | func main() { 19 | var ( 20 | addr = flag.String("url", "http://localhost:8080", "`URL` to service host") 21 | verbose = flag.Bool("verbose", false, "Print request and response details") 22 | v = flag.Bool("v", false, "Print request and response details") 23 | timeout = flag.Int("timeout", 30, "Maximum number of `seconds` to wait for response") 24 | ) 25 | flag.Usage = usage 26 | flag.Parse() 27 | 28 | var ( 29 | scheme string 30 | host string 31 | debug bool 32 | ) 33 | { 34 | u, err := url.Parse(*addr) 35 | if err != nil { 36 | fmt.Fprintf(os.Stderr, "invalid URL %#v: %s", *addr, err) 37 | os.Exit(1) 38 | } 39 | scheme = u.Scheme 40 | host = u.Host 41 | if scheme == "" { 42 | scheme = "http" 43 | } 44 | debug = *verbose || *v 45 | } 46 | 47 | var ( 48 | doer goahttp.Doer 49 | ) 50 | { 51 | doer = &http.Client{Timeout: time.Duration(*timeout) * time.Second} 52 | if debug { 53 | doer = goahttp.NewDebugDoer(doer) 54 | } 55 | } 56 | 57 | endpoint, payload, err := cli.ParseEndpoint( 58 | scheme, 59 | host, 60 | doer, 61 | goahttp.RequestEncoder, 62 | goahttp.ResponseDecoder, 63 | debug, 64 | ) 65 | if err != nil { 66 | if err == flag.ErrHelp { 67 | os.Exit(0) 68 | } 69 | fmt.Fprintln(os.Stderr, err.Error()) 70 | fmt.Fprintln(os.Stderr, "run '"+os.Args[0]+" --help' for detailed usage.") 71 | os.Exit(1) 72 | } 73 | 74 | data, err := endpoint(context.Background(), payload) 75 | 76 | if debug { 77 | doer.(goahttp.DebugDoer).Fprint(os.Stderr) 78 | } 79 | 80 | if err != nil { 81 | fmt.Fprintln(os.Stderr, err.Error()) 82 | os.Exit(1) 83 | } 84 | 85 | if data != nil && !debug { 86 | m, _ := json.MarshalIndent(data, "", " ") 87 | fmt.Println(string(m)) 88 | } 89 | } 90 | 91 | func usage() { 92 | fmt.Fprintf(os.Stderr, `%s is a command line client for the recorder API. 93 | 94 | Usage: 95 | %s [-url URL][-timeout SECONDS][-verbose|-v] SERVICE ENDPOINT [flags] 96 | 97 | -url URL: specify service URL (http://localhost:8080) 98 | -timeout: maximum number of seconds to wait for response (30) 99 | -verbose|-v: print request and response details (false) 100 | 101 | Commands: 102 | %s 103 | Additional help: 104 | %s SERVICE [ENDPOINT] --help 105 | 106 | Example: 107 | %s 108 | `, os.Args[0], os.Args[0], indent(cli.UsageCommands()), os.Args[0], indent(cli.UsageExamples())) 109 | } 110 | 111 | func indent(s string) string { 112 | if s == "" { 113 | return "" 114 | } 115 | return " " + strings.Replace(s, "\n", "\n ", -1) 116 | } 117 | -------------------------------------------------------------------------------- /recorder.go: -------------------------------------------------------------------------------- 1 | package recorder 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "strings" 9 | 10 | "cloud.google.com/go/storage" 11 | recorder "github.com/raphael/recorder/gen/recorder" 12 | "google.golang.org/appengine/file" 13 | "google.golang.org/appengine/log" 14 | ) 15 | 16 | type ( 17 | // recorder service implementation 18 | recorderSvc struct{} 19 | ) 20 | 21 | // NewRecorder returns the recorder service implementation. 22 | func NewRecorder() recorder.Service { 23 | return &recorderSvc{} 24 | } 25 | 26 | // RecordData creates a new datapoint. 27 | func (s *recorderSvc) RecordData(ctx context.Context, p *recorder.Datapoint) error { 28 | all, err := read(ctx, p.Service, p.Name) 29 | if err != nil { 30 | return err 31 | } 32 | all = append(all, p.Value) 33 | return write(ctx, p.Service, p.Name, all) 34 | } 35 | 36 | // List lists all datapoints for the given service and name. 37 | func (s *recorderSvc) List(ctx context.Context, p *recorder.Series) ([]float64, error) { 38 | return read(ctx, p.Service, p.Name) 39 | } 40 | 41 | func read(ctx context.Context, service, name string) ([]float64, error) { 42 | var pts []float64 43 | err := withBucket(ctx, func(bucket *storage.BucketHandle) error { 44 | fn := filename(service, name) 45 | rc, err := bucket.Object(fn).NewReader(ctx) 46 | if err != nil { 47 | if strings.Contains(err.Error(), "object doesn't exist") { 48 | return nil 49 | } 50 | return fmt.Errorf("readall: unable to open file %q: %v", fn, err) 51 | } 52 | defer rc.Close() 53 | slurp, err := ioutil.ReadAll(rc) 54 | if err != nil { 55 | return fmt.Errorf("readall: unable to read data from file %q: %v", fn, err) 56 | } 57 | if len(slurp) > 0 { 58 | if err := json.Unmarshal(slurp, &pts); err != nil { 59 | return fmt.Errorf("readall: failed to deserialize content of file %q: %v", fn, err) 60 | } 61 | } 62 | return nil 63 | }) 64 | if err != nil { 65 | log.Errorf(ctx, err.Error()) 66 | } 67 | return pts, err 68 | } 69 | 70 | func write(ctx context.Context, service, name string, pts []float64) error { 71 | err := withBucket(ctx, func(bucket *storage.BucketHandle) error { 72 | fn := filename(service, name) 73 | wc := bucket.Object(fn).NewWriter(ctx) 74 | defer wc.Close() 75 | wc.ContentType = "text/plain" 76 | js, err := json.Marshal(pts) 77 | if err != nil { 78 | return err 79 | } 80 | _, err = wc.Write(js) 81 | return err 82 | }) 83 | if err != nil { 84 | log.Errorf(ctx, err.Error()) 85 | } 86 | return err 87 | } 88 | 89 | func withBucket(ctx context.Context, fn func(*storage.BucketHandle) error) error { 90 | bucket, err := file.DefaultBucketName(ctx) 91 | if err != nil { 92 | return fmt.Errorf("readall: failed to get default GCS bucket name: %v", err) 93 | } 94 | client, err := storage.NewClient(ctx) 95 | if err != nil { 96 | return fmt.Errorf("failed to create client: %v", err) 97 | } 98 | defer client.Close() 99 | return fn(client.Bucket(bucket)) 100 | } 101 | 102 | func filename(service, name string) string { 103 | return fmt.Sprintf("%s-%s.txt", service, name) 104 | } 105 | -------------------------------------------------------------------------------- /gen/http/recorder/client/client.go: -------------------------------------------------------------------------------- 1 | // Code generated with goa v2.0.0-wip, DO NOT EDIT. 2 | // 3 | // recorder client HTTP transport 4 | // 5 | // Command: 6 | // $ goa gen github.com/raphael/recorder/design 7 | 8 | package client 9 | 10 | import ( 11 | "context" 12 | "net/http" 13 | 14 | goa "goa.design/goa" 15 | goahttp "goa.design/goa/http" 16 | ) 17 | 18 | // Client lists the recorder service endpoint HTTP clients. 19 | type Client struct { 20 | // RecordData Doer is the HTTP client used to make requests to the record-data 21 | // endpoint. 22 | RecordDataDoer goahttp.Doer 23 | 24 | // List Doer is the HTTP client used to make requests to the list endpoint. 25 | ListDoer goahttp.Doer 26 | 27 | // RestoreResponseBody controls whether the response bodies are reset after 28 | // decoding so they can be read again. 29 | RestoreResponseBody bool 30 | 31 | scheme string 32 | host string 33 | encoder func(*http.Request) goahttp.Encoder 34 | decoder func(*http.Response) goahttp.Decoder 35 | } 36 | 37 | // NewClient instantiates HTTP clients for all the recorder service servers. 38 | func NewClient( 39 | scheme string, 40 | host string, 41 | doer goahttp.Doer, 42 | enc func(*http.Request) goahttp.Encoder, 43 | dec func(*http.Response) goahttp.Decoder, 44 | restoreBody bool, 45 | ) *Client { 46 | return &Client{ 47 | RecordDataDoer: doer, 48 | ListDoer: doer, 49 | RestoreResponseBody: restoreBody, 50 | scheme: scheme, 51 | host: host, 52 | decoder: dec, 53 | encoder: enc, 54 | } 55 | } 56 | 57 | // RecordData returns an endpoint that makes HTTP requests to the recorder 58 | // service record-data server. 59 | func (c *Client) RecordData() goa.Endpoint { 60 | var ( 61 | encodeRequest = EncodeRecordDataRequest(c.encoder) 62 | decodeResponse = DecodeRecordDataResponse(c.decoder, c.RestoreResponseBody) 63 | ) 64 | return func(ctx context.Context, v interface{}) (interface{}, error) { 65 | req, err := c.BuildRecordDataRequest(ctx, v) 66 | if err != nil { 67 | return nil, err 68 | } 69 | err = encodeRequest(req, v) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | resp, err := c.RecordDataDoer.Do(req) 75 | 76 | if err != nil { 77 | return nil, goahttp.ErrRequestError("recorder", "record-data", err) 78 | } 79 | return decodeResponse(resp) 80 | } 81 | } 82 | 83 | // List returns an endpoint that makes HTTP requests to the recorder service 84 | // list server. 85 | func (c *Client) List() goa.Endpoint { 86 | var ( 87 | encodeRequest = EncodeListRequest(c.encoder) 88 | decodeResponse = DecodeListResponse(c.decoder, c.RestoreResponseBody) 89 | ) 90 | return func(ctx context.Context, v interface{}) (interface{}, error) { 91 | req, err := c.BuildListRequest(ctx, v) 92 | if err != nil { 93 | return nil, err 94 | } 95 | err = encodeRequest(req, v) 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | resp, err := c.ListDoer.Do(req) 101 | 102 | if err != nil { 103 | return nil, goahttp.ErrRequestError("recorder", "list", err) 104 | } 105 | return decodeResponse(resp) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /cmd/recordersvc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "log" 7 | "net/http" 8 | "os" 9 | 10 | recorder "github.com/raphael/recorder" 11 | recordersvcsvr "github.com/raphael/recorder/gen/http/recorder/server" 12 | recordersvc "github.com/raphael/recorder/gen/recorder" 13 | goahttp "goa.design/goa/http" 14 | "goa.design/goa/http/middleware" 15 | "google.golang.org/appengine" 16 | ) 17 | 18 | func main() { 19 | // Define command line flags, add any other flag required to configure 20 | // the service. 21 | var ( 22 | addr = flag.String("listen", ":8080", "HTTP listen `address`") 23 | dbg = flag.Bool("debug", false, "Log request and response bodies") 24 | ) 25 | flag.Parse() 26 | 27 | // Setup logger and goa log adapter. Replace logger with your own using 28 | // your log package of choice. The goa.design/middleware/logging/... 29 | // packages define log adapters for common log packages. 30 | var ( 31 | adapter middleware.Logger 32 | logger *log.Logger 33 | ) 34 | { 35 | logger = log.New(os.Stderr, "[recorder] ", log.Ltime) 36 | adapter = middleware.NewLogger(logger) 37 | } 38 | 39 | // Create the structs that implement the services. 40 | var ( 41 | recorderSvc recordersvc.Service 42 | ) 43 | { 44 | recorderSvc = recorder.NewRecorder() 45 | } 46 | 47 | // Wrap the services in endpoints that can be invoked from other 48 | // services potentially running in different processes. 49 | var ( 50 | recorderEndpoints *recordersvc.Endpoints 51 | ) 52 | { 53 | recorderEndpoints = recordersvc.NewEndpoints(recorderSvc) 54 | } 55 | 56 | // Provide the transport specific request decoder and response encoder. 57 | // The goa http package has built-in support for JSON, XML and gob. 58 | // Other encodings can be used by providing the corresponding functions, 59 | // see goa.design/encoding. 60 | var ( 61 | dec = goahttp.RequestDecoder 62 | enc = goahttp.ResponseEncoder 63 | ) 64 | 65 | // Build the service HTTP request multiplexer and configure it to serve 66 | // HTTP requests to the service endpoints. 67 | var mux goahttp.Muxer 68 | { 69 | mux = goahttp.NewMuxer() 70 | } 71 | 72 | // Wrap the endpoints with the transport specific layers. The generated 73 | // server packages contains code generated from the design which maps 74 | // the service input and output data structures to HTTP requests and 75 | // responses. 76 | var ( 77 | recorderServer *recordersvcsvr.Server 78 | ) 79 | { 80 | eh := ErrorHandler(logger) 81 | recorderServer = recordersvcsvr.New(recorderEndpoints, mux, dec, enc, eh) 82 | } 83 | 84 | // Configure the mux. 85 | recordersvcsvr.Mount(mux, recorderServer) 86 | 87 | // Wrap the multiplexer with additional middlewares. Middlewares mounted 88 | // here apply to all the service endpoints. 89 | var handler http.Handler 90 | { 91 | var h http.Handler = mux 92 | if *dbg { 93 | h = middleware.Debug(mux, os.Stdout)(h) 94 | } 95 | h = middleware.Log(adapter)(h) 96 | h = middleware.RequestID()(h) 97 | handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 98 | aectx := appengine.NewContext(r) 99 | r = r.WithContext(aectx) 100 | h.ServeHTTP(w, r) 101 | }) 102 | } 103 | 104 | // Start AppEngine. 105 | for _, m := range recorderServer.Mounts { 106 | logger.Printf("method %q mounted on %s %s", m.Method, m.Verb, m.Pattern) 107 | } 108 | logger.Printf("listening on %s", *addr) 109 | http.Handle("/", handler) 110 | appengine.Main() 111 | 112 | logger.Println("exited") 113 | } 114 | 115 | // ErrorHandler returns a function that writes and logs the given error. 116 | // The function also writes and logs the error unique ID so that it's possible 117 | // to correlate. 118 | func ErrorHandler(logger *log.Logger) func(context.Context, http.ResponseWriter, error) { 119 | return func(ctx context.Context, w http.ResponseWriter, err error) { 120 | id := ctx.Value(middleware.RequestIDKey).(string) 121 | w.Write([]byte("[" + id + "] encoding: " + err.Error())) 122 | logger.Printf("[%s] ERROR: %s", id, err.Error()) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /gen/http/recorder/client/encode_decode.go: -------------------------------------------------------------------------------- 1 | // Code generated with goa v2.0.0-wip, DO NOT EDIT. 2 | // 3 | // recorder HTTP client encoders and decoders 4 | // 5 | // Command: 6 | // $ goa gen github.com/raphael/recorder/design 7 | 8 | package client 9 | 10 | import ( 11 | "bytes" 12 | "context" 13 | "io/ioutil" 14 | "net/http" 15 | "net/url" 16 | 17 | recordersvc "github.com/raphael/recorder/gen/recorder" 18 | goahttp "goa.design/goa/http" 19 | ) 20 | 21 | // BuildRecordDataRequest instantiates a HTTP request object with method and 22 | // path set to call the "recorder" service "record-data" endpoint 23 | func (c *Client) BuildRecordDataRequest(ctx context.Context, v interface{}) (*http.Request, error) { 24 | u := &url.URL{Scheme: c.scheme, Host: c.host, Path: RecordDataRecorderPath()} 25 | req, err := http.NewRequest("POST", u.String(), nil) 26 | if err != nil { 27 | return nil, goahttp.ErrInvalidURL("recorder", "record-data", u.String(), err) 28 | } 29 | if ctx != nil { 30 | req = req.WithContext(ctx) 31 | } 32 | 33 | return req, nil 34 | } 35 | 36 | // EncodeRecordDataRequest returns an encoder for requests sent to the recorder 37 | // record-data server. 38 | func EncodeRecordDataRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, interface{}) error { 39 | return func(req *http.Request, v interface{}) error { 40 | p, ok := v.(*recordersvc.Datapoint) 41 | if !ok { 42 | return goahttp.ErrInvalidType("recorder", "record-data", "*recordersvc.Datapoint", v) 43 | } 44 | body := NewRecordDataRequestBody(p) 45 | if err := encoder(req).Encode(&body); err != nil { 46 | return goahttp.ErrEncodingError("recorder", "record-data", err) 47 | } 48 | return nil 49 | } 50 | } 51 | 52 | // DecodeRecordDataResponse returns a decoder for responses returned by the 53 | // recorder record-data endpoint. restoreBody controls whether the response 54 | // body should be restored after having been read. 55 | func DecodeRecordDataResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (interface{}, error) { 56 | return func(resp *http.Response) (interface{}, error) { 57 | if restoreBody { 58 | b, err := ioutil.ReadAll(resp.Body) 59 | if err != nil { 60 | return nil, err 61 | } 62 | resp.Body = ioutil.NopCloser(bytes.NewBuffer(b)) 63 | defer func() { 64 | resp.Body = ioutil.NopCloser(bytes.NewBuffer(b)) 65 | }() 66 | } else { 67 | defer resp.Body.Close() 68 | } 69 | switch resp.StatusCode { 70 | case http.StatusOK: 71 | return nil, nil 72 | default: 73 | body, _ := ioutil.ReadAll(resp.Body) 74 | return nil, goahttp.ErrInvalidResponse("account", "create", resp.StatusCode, string(body)) 75 | } 76 | } 77 | } 78 | 79 | // BuildListRequest instantiates a HTTP request object with method and path set 80 | // to call the "recorder" service "list" endpoint 81 | func (c *Client) BuildListRequest(ctx context.Context, v interface{}) (*http.Request, error) { 82 | u := &url.URL{Scheme: c.scheme, Host: c.host, Path: ListRecorderPath()} 83 | req, err := http.NewRequest("GET", u.String(), nil) 84 | if err != nil { 85 | return nil, goahttp.ErrInvalidURL("recorder", "list", u.String(), err) 86 | } 87 | if ctx != nil { 88 | req = req.WithContext(ctx) 89 | } 90 | 91 | return req, nil 92 | } 93 | 94 | // EncodeListRequest returns an encoder for requests sent to the recorder list 95 | // server. 96 | func EncodeListRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, interface{}) error { 97 | return func(req *http.Request, v interface{}) error { 98 | p, ok := v.(*recordersvc.Series) 99 | if !ok { 100 | return goahttp.ErrInvalidType("recorder", "list", "*recordersvc.Series", v) 101 | } 102 | values := req.URL.Query() 103 | values.Add("service", p.Service) 104 | values.Add("name", p.Name) 105 | req.URL.RawQuery = values.Encode() 106 | return nil 107 | } 108 | } 109 | 110 | // DecodeListResponse returns a decoder for responses returned by the recorder 111 | // list endpoint. restoreBody controls whether the response body should be 112 | // restored after having been read. 113 | func DecodeListResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (interface{}, error) { 114 | return func(resp *http.Response) (interface{}, error) { 115 | if restoreBody { 116 | b, err := ioutil.ReadAll(resp.Body) 117 | if err != nil { 118 | return nil, err 119 | } 120 | resp.Body = ioutil.NopCloser(bytes.NewBuffer(b)) 121 | defer func() { 122 | resp.Body = ioutil.NopCloser(bytes.NewBuffer(b)) 123 | }() 124 | } else { 125 | defer resp.Body.Close() 126 | } 127 | switch resp.StatusCode { 128 | case http.StatusOK: 129 | var ( 130 | body []float64 131 | err error 132 | ) 133 | err = decoder(resp).Decode(&body) 134 | if err != nil { 135 | return nil, goahttp.ErrDecodingError("recorder", "list", err) 136 | } 137 | 138 | return body, nil 139 | default: 140 | body, _ := ioutil.ReadAll(resp.Body) 141 | return nil, goahttp.ErrInvalidResponse("account", "create", resp.StatusCode, string(body)) 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /gen/http/cli/cli.go: -------------------------------------------------------------------------------- 1 | // Code generated with goa v2.0.0-wip, DO NOT EDIT. 2 | // 3 | // recorder HTTP client CLI support package 4 | // 5 | // Command: 6 | // $ goa gen github.com/raphael/recorder/design 7 | 8 | package cli 9 | 10 | import ( 11 | "flag" 12 | "fmt" 13 | "net/http" 14 | "os" 15 | 16 | recordersvcc "github.com/raphael/recorder/gen/http/recorder/client" 17 | goa "goa.design/goa" 18 | goahttp "goa.design/goa/http" 19 | ) 20 | 21 | // UsageCommands returns the set of commands and sub-commands using the format 22 | // 23 | // command (subcommand1|subcommand2|...) 24 | // 25 | func UsageCommands() string { 26 | return `recorder (record-data|list) 27 | ` 28 | } 29 | 30 | // UsageExamples produces an example of a valid invocation of the CLI tool. 31 | func UsageExamples() string { 32 | return os.Args[0] + ` recorder record-data --body '{ 33 | "name": "duration", 34 | "service": "lambda", 35 | "value": 0.06173619203715241 36 | }'` + "\n" + 37 | "" 38 | } 39 | 40 | // ParseEndpoint returns the endpoint and payload as specified on the command 41 | // line. 42 | func ParseEndpoint( 43 | scheme, host string, 44 | doer goahttp.Doer, 45 | enc func(*http.Request) goahttp.Encoder, 46 | dec func(*http.Response) goahttp.Decoder, 47 | restore bool, 48 | ) (goa.Endpoint, interface{}, error) { 49 | var ( 50 | recorderFlags = flag.NewFlagSet("recorder", flag.ContinueOnError) 51 | 52 | recorderRecordDataFlags = flag.NewFlagSet("record-data", flag.ExitOnError) 53 | recorderRecordDataBodyFlag = recorderRecordDataFlags.String("body", "REQUIRED", "") 54 | 55 | recorderListFlags = flag.NewFlagSet("list", flag.ExitOnError) 56 | recorderListServiceFlag = recorderListFlags.String("service", "REQUIRED", "") 57 | recorderListNameFlag = recorderListFlags.String("name", "REQUIRED", "") 58 | ) 59 | recorderFlags.Usage = recorderUsage 60 | recorderRecordDataFlags.Usage = recorderRecordDataUsage 61 | recorderListFlags.Usage = recorderListUsage 62 | 63 | if err := flag.CommandLine.Parse(os.Args[1:]); err != nil { 64 | return nil, nil, err 65 | } 66 | 67 | if len(os.Args) < flag.NFlag()+3 { 68 | return nil, nil, fmt.Errorf("not enough arguments") 69 | } 70 | 71 | var ( 72 | svcn string 73 | svcf *flag.FlagSet 74 | ) 75 | { 76 | svcn = os.Args[1+flag.NFlag()] 77 | switch svcn { 78 | case "recorder": 79 | svcf = recorderFlags 80 | default: 81 | return nil, nil, fmt.Errorf("unknown service %q", svcn) 82 | } 83 | } 84 | if err := svcf.Parse(os.Args[2+flag.NFlag():]); err != nil { 85 | return nil, nil, err 86 | } 87 | 88 | var ( 89 | epn string 90 | epf *flag.FlagSet 91 | ) 92 | { 93 | epn = os.Args[2+flag.NFlag()+svcf.NFlag()] 94 | switch svcn { 95 | case "recorder": 96 | switch epn { 97 | case "record-data": 98 | epf = recorderRecordDataFlags 99 | 100 | case "list": 101 | epf = recorderListFlags 102 | 103 | } 104 | 105 | } 106 | } 107 | if epf == nil { 108 | return nil, nil, fmt.Errorf("unknown %q endpoint %q", svcn, epn) 109 | } 110 | 111 | // Parse endpoint flags if any 112 | if len(os.Args) > 2+flag.NFlag()+svcf.NFlag() { 113 | if err := epf.Parse(os.Args[3+flag.NFlag()+svcf.NFlag():]); err != nil { 114 | return nil, nil, err 115 | } 116 | } 117 | 118 | var ( 119 | data interface{} 120 | endpoint goa.Endpoint 121 | err error 122 | ) 123 | { 124 | switch svcn { 125 | case "recorder": 126 | c := recordersvcc.NewClient(scheme, host, doer, enc, dec, restore) 127 | switch epn { 128 | case "record-data": 129 | endpoint = c.RecordData() 130 | data, err = recordersvcc.BuildRecordDataPayload(*recorderRecordDataBodyFlag) 131 | case "list": 132 | endpoint = c.List() 133 | data, err = recordersvcc.BuildListPayload(*recorderListServiceFlag, *recorderListNameFlag) 134 | } 135 | } 136 | } 137 | if err != nil { 138 | return nil, nil, err 139 | } 140 | 141 | return endpoint, data, nil 142 | } 143 | 144 | // recorderUsage displays the usage of the recorder command and its subcommands. 145 | func recorderUsage() { 146 | fmt.Fprintf(os.Stderr, `Service is the recorder service interface. 147 | Usage: 148 | %s [globalflags] recorder COMMAND [flags] 149 | 150 | COMMAND: 151 | record-data: RecordData creates a new datapoint. 152 | list: List lists all recorded datapoints. 153 | 154 | Additional help: 155 | %s recorder COMMAND --help 156 | `, os.Args[0], os.Args[0]) 157 | } 158 | func recorderRecordDataUsage() { 159 | fmt.Fprintf(os.Stderr, `%s [flags] recorder record-data -body JSON 160 | 161 | RecordData creates a new datapoint. 162 | -body JSON: 163 | 164 | Example: 165 | `+os.Args[0]+` recorder record-data --body '{ 166 | "name": "duration", 167 | "service": "lambda", 168 | "value": 0.06173619203715241 169 | }' 170 | `, os.Args[0]) 171 | } 172 | 173 | func recorderListUsage() { 174 | fmt.Fprintf(os.Stderr, `%s [flags] recorder list -service STRING -name STRING 175 | 176 | List lists all recorded datapoints. 177 | -service STRING: 178 | -name STRING: 179 | 180 | Example: 181 | `+os.Args[0]+` recorder list --service "lambda" --name "duration" 182 | `, os.Args[0]) 183 | } 184 | -------------------------------------------------------------------------------- /gen/http/recorder/server/server.go: -------------------------------------------------------------------------------- 1 | // Code generated with goa v2.0.0-wip, DO NOT EDIT. 2 | // 3 | // recorder HTTP server 4 | // 5 | // Command: 6 | // $ goa gen github.com/raphael/recorder/design 7 | 8 | package server 9 | 10 | import ( 11 | "context" 12 | "net/http" 13 | 14 | recordersvc "github.com/raphael/recorder/gen/recorder" 15 | goa "goa.design/goa" 16 | goahttp "goa.design/goa/http" 17 | ) 18 | 19 | // Server lists the recorder service endpoint HTTP handlers. 20 | type Server struct { 21 | Mounts []*MountPoint 22 | RecordData http.Handler 23 | List http.Handler 24 | } 25 | 26 | // MountPoint holds information about the mounted endpoints. 27 | type MountPoint struct { 28 | // Method is the name of the service method served by the mounted HTTP handler. 29 | Method string 30 | // Verb is the HTTP method used to match requests to the mounted handler. 31 | Verb string 32 | // Pattern is the HTTP request path pattern used to match requests to the 33 | // mounted handler. 34 | Pattern string 35 | } 36 | 37 | // New instantiates HTTP handlers for all the recorder service endpoints. 38 | func New( 39 | e *recordersvc.Endpoints, 40 | mux goahttp.Muxer, 41 | dec func(*http.Request) goahttp.Decoder, 42 | enc func(context.Context, http.ResponseWriter) goahttp.Encoder, 43 | eh func(context.Context, http.ResponseWriter, error), 44 | ) *Server { 45 | return &Server{ 46 | Mounts: []*MountPoint{ 47 | {"RecordData", "POST", "/data"}, 48 | {"List", "GET", "/data"}, 49 | }, 50 | RecordData: NewRecordDataHandler(e.RecordData, mux, dec, enc, eh), 51 | List: NewListHandler(e.List, mux, dec, enc, eh), 52 | } 53 | } 54 | 55 | // Service returns the name of the service served. 56 | func (s *Server) Service() string { return "recorder" } 57 | 58 | // Use wraps the server handlers with the given middleware. 59 | func (s *Server) Use(m func(http.Handler) http.Handler) { 60 | s.RecordData = m(s.RecordData) 61 | s.List = m(s.List) 62 | } 63 | 64 | // Mount configures the mux to serve the recorder endpoints. 65 | func Mount(mux goahttp.Muxer, h *Server) { 66 | MountRecordDataHandler(mux, h.RecordData) 67 | MountListHandler(mux, h.List) 68 | } 69 | 70 | // MountRecordDataHandler configures the mux to serve the "recorder" service 71 | // "record-data" endpoint. 72 | func MountRecordDataHandler(mux goahttp.Muxer, h http.Handler) { 73 | f, ok := h.(http.HandlerFunc) 74 | if !ok { 75 | f = func(w http.ResponseWriter, r *http.Request) { 76 | h.ServeHTTP(w, r) 77 | } 78 | } 79 | mux.Handle("POST", "/data", f) 80 | } 81 | 82 | // NewRecordDataHandler creates a HTTP handler which loads the HTTP request and 83 | // calls the "recorder" service "record-data" endpoint. 84 | func NewRecordDataHandler( 85 | endpoint goa.Endpoint, 86 | mux goahttp.Muxer, 87 | dec func(*http.Request) goahttp.Decoder, 88 | enc func(context.Context, http.ResponseWriter) goahttp.Encoder, 89 | eh func(context.Context, http.ResponseWriter, error), 90 | ) http.Handler { 91 | var ( 92 | decodeRequest = DecodeRecordDataRequest(mux, dec) 93 | encodeResponse = EncodeRecordDataResponse(enc) 94 | encodeError = goahttp.ErrorEncoder(enc) 95 | ) 96 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 97 | ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) 98 | ctx = context.WithValue(ctx, goa.MethodKey, "record-data") 99 | ctx = context.WithValue(ctx, goa.ServiceKey, "recorder") 100 | payload, err := decodeRequest(r) 101 | if err != nil { 102 | eh(ctx, w, err) 103 | return 104 | } 105 | 106 | res, err := endpoint(ctx, payload) 107 | 108 | if err != nil { 109 | if err := encodeError(ctx, w, err); err != nil { 110 | eh(ctx, w, err) 111 | return 112 | } 113 | } 114 | if err := encodeResponse(ctx, w, res); err != nil { 115 | eh(ctx, w, err) 116 | } 117 | }) 118 | } 119 | 120 | // MountListHandler configures the mux to serve the "recorder" service "list" 121 | // endpoint. 122 | func MountListHandler(mux goahttp.Muxer, h http.Handler) { 123 | f, ok := h.(http.HandlerFunc) 124 | if !ok { 125 | f = func(w http.ResponseWriter, r *http.Request) { 126 | h.ServeHTTP(w, r) 127 | } 128 | } 129 | mux.Handle("GET", "/data", f) 130 | } 131 | 132 | // NewListHandler creates a HTTP handler which loads the HTTP request and calls 133 | // the "recorder" service "list" endpoint. 134 | func NewListHandler( 135 | endpoint goa.Endpoint, 136 | mux goahttp.Muxer, 137 | dec func(*http.Request) goahttp.Decoder, 138 | enc func(context.Context, http.ResponseWriter) goahttp.Encoder, 139 | eh func(context.Context, http.ResponseWriter, error), 140 | ) http.Handler { 141 | var ( 142 | decodeRequest = DecodeListRequest(mux, dec) 143 | encodeResponse = EncodeListResponse(enc) 144 | encodeError = goahttp.ErrorEncoder(enc) 145 | ) 146 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 147 | ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) 148 | ctx = context.WithValue(ctx, goa.MethodKey, "list") 149 | ctx = context.WithValue(ctx, goa.ServiceKey, "recorder") 150 | payload, err := decodeRequest(r) 151 | if err != nil { 152 | eh(ctx, w, err) 153 | return 154 | } 155 | 156 | res, err := endpoint(ctx, payload) 157 | 158 | if err != nil { 159 | if err := encodeError(ctx, w, err); err != nil { 160 | eh(ctx, w, err) 161 | return 162 | } 163 | } 164 | if err := encodeResponse(ctx, w, res); err != nil { 165 | eh(ctx, w, err) 166 | } 167 | }) 168 | } 169 | -------------------------------------------------------------------------------- /gen/http/openapi.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: Recorder API 4 | description: Recorder records arbitrary data together with the recording timestamp 5 | termsofservice: "" 6 | contact: null 7 | license: null 8 | version: "" 9 | extensions: {} 10 | host: localhost:8080 11 | basepath: "" 12 | schemes: [] 13 | consumes: [] 14 | produces: [] 15 | paths: 16 | /data: 17 | ref: "" 18 | get: 19 | tags: 20 | - recorder 21 | summary: list recorder 22 | description: List lists all recorded datapoints. 23 | externaldocs: null 24 | operationid: recorder#list 25 | consumes: [] 26 | produces: [] 27 | parameters: 28 | - name: service 29 | in: query 30 | description: Service that created datapoint. 31 | required: true 32 | schema: null 33 | type: string 34 | format: "" 35 | allowemptyvalue: false 36 | items: null 37 | collectionformat: "" 38 | default: null 39 | maximum: null 40 | exclusivemaximum: false 41 | minimum: null 42 | exclusiveminimum: false 43 | maxlength: null 44 | minlength: null 45 | pattern: "" 46 | maxitems: null 47 | minitems: null 48 | uniqueitems: false 49 | enum: [] 50 | multipleof: 0 51 | extensions: {} 52 | - name: name 53 | in: query 54 | description: Name is the name of the datapoint. 55 | required: true 56 | schema: null 57 | type: string 58 | format: "" 59 | allowemptyvalue: false 60 | items: null 61 | collectionformat: "" 62 | default: null 63 | maximum: null 64 | exclusivemaximum: false 65 | minimum: null 66 | exclusiveminimum: false 67 | maxlength: null 68 | minlength: null 69 | pattern: "" 70 | maxitems: null 71 | minitems: null 72 | uniqueitems: false 73 | enum: [] 74 | multipleof: 0 75 | extensions: {} 76 | responses: 77 | "200": 78 | description: OK response. 79 | schema: 80 | schema: "" 81 | id: "" 82 | title: "" 83 | type: array 84 | items: 85 | schema: "" 86 | id: "" 87 | title: "" 88 | type: number 89 | items: null 90 | properties: {} 91 | definitions: {} 92 | description: "" 93 | defaultvalue: null 94 | example: 0.57306284 95 | media: null 96 | readonly: false 97 | pathstart: "" 98 | links: [] 99 | ref: "" 100 | enum: [] 101 | format: double 102 | pattern: "" 103 | minimum: null 104 | maximum: null 105 | minlength: null 106 | maxlength: null 107 | required: [] 108 | additionalproperties: false 109 | anyof: [] 110 | properties: {} 111 | definitions: {} 112 | description: "" 113 | defaultvalue: null 114 | example: null 115 | media: null 116 | readonly: false 117 | pathstart: "" 118 | links: [] 119 | ref: "" 120 | enum: [] 121 | format: "" 122 | pattern: "" 123 | minimum: null 124 | maximum: null 125 | minlength: null 126 | maxlength: null 127 | required: [] 128 | additionalproperties: false 129 | anyof: [] 130 | headers: {} 131 | ref: "" 132 | extensions: {} 133 | schemes: 134 | - http 135 | deprecated: false 136 | security: [] 137 | extensions: {} 138 | put: null 139 | post: 140 | tags: 141 | - recorder 142 | summary: record-data recorder 143 | description: RecordData creates a new datapoint. 144 | externaldocs: null 145 | operationid: recorder#record-data 146 | consumes: [] 147 | produces: [] 148 | parameters: 149 | - name: RecordDataRequestBody 150 | in: body 151 | description: "" 152 | required: true 153 | schema: 154 | schema: "" 155 | id: "" 156 | title: "" 157 | type: "" 158 | items: null 159 | properties: {} 160 | definitions: {} 161 | description: "" 162 | defaultvalue: null 163 | example: null 164 | media: null 165 | readonly: false 166 | pathstart: "" 167 | links: [] 168 | ref: '#/definitions/RecordDataRequestBody' 169 | enum: [] 170 | format: "" 171 | pattern: "" 172 | minimum: null 173 | maximum: null 174 | minlength: null 175 | maxlength: null 176 | required: [] 177 | additionalproperties: false 178 | anyof: [] 179 | type: "" 180 | format: "" 181 | allowemptyvalue: false 182 | items: null 183 | collectionformat: "" 184 | default: null 185 | maximum: null 186 | exclusivemaximum: false 187 | minimum: null 188 | exclusiveminimum: false 189 | maxlength: null 190 | minlength: null 191 | pattern: "" 192 | maxitems: null 193 | minitems: null 194 | uniqueitems: false 195 | enum: [] 196 | multipleof: 0 197 | extensions: {} 198 | responses: 199 | "200": 200 | description: OK response. 201 | schema: null 202 | headers: {} 203 | ref: "" 204 | extensions: {} 205 | schemes: 206 | - http 207 | deprecated: false 208 | security: [] 209 | extensions: {} 210 | delete: null 211 | options: null 212 | head: null 213 | patch: null 214 | parameters: [] 215 | extensions: {} 216 | definitions: 217 | RecordDataRequestBody: 218 | schema: "" 219 | id: "" 220 | title: RecordDataRequestBody 221 | type: object 222 | items: null 223 | properties: 224 | name: 225 | schema: "" 226 | id: "" 227 | title: "" 228 | type: string 229 | items: null 230 | properties: {} 231 | definitions: {} 232 | description: Name is the name of the datapoint. 233 | defaultvalue: null 234 | example: duration 235 | media: null 236 | readonly: false 237 | pathstart: "" 238 | links: [] 239 | ref: "" 240 | enum: [] 241 | format: "" 242 | pattern: "" 243 | minimum: null 244 | maximum: null 245 | minlength: null 246 | maxlength: null 247 | required: [] 248 | additionalproperties: false 249 | anyof: [] 250 | service: 251 | schema: "" 252 | id: "" 253 | title: "" 254 | type: string 255 | items: null 256 | properties: {} 257 | definitions: {} 258 | description: Service that created datapoint. 259 | defaultvalue: null 260 | example: lambda 261 | media: null 262 | readonly: false 263 | pathstart: "" 264 | links: [] 265 | ref: "" 266 | enum: [] 267 | format: "" 268 | pattern: "" 269 | minimum: null 270 | maximum: null 271 | minlength: null 272 | maxlength: null 273 | required: [] 274 | additionalproperties: false 275 | anyof: [] 276 | value: 277 | schema: "" 278 | id: "" 279 | title: "" 280 | type: number 281 | items: null 282 | properties: {} 283 | definitions: {} 284 | description: Datapoint value. 285 | defaultvalue: null 286 | example: 0.7022016 287 | media: null 288 | readonly: false 289 | pathstart: "" 290 | links: [] 291 | ref: "" 292 | enum: [] 293 | format: double 294 | pattern: "" 295 | minimum: null 296 | maximum: null 297 | minlength: null 298 | maxlength: null 299 | required: [] 300 | additionalproperties: false 301 | anyof: [] 302 | definitions: {} 303 | description: "" 304 | defaultvalue: null 305 | example: 306 | name: duration 307 | service: lambda 308 | value: 0.34037277 309 | media: null 310 | readonly: false 311 | pathstart: "" 312 | links: [] 313 | ref: "" 314 | enum: [] 315 | format: "" 316 | pattern: "" 317 | minimum: null 318 | maximum: null 319 | minlength: null 320 | maxlength: null 321 | required: 322 | - service 323 | - name 324 | - value 325 | additionalproperties: false 326 | anyof: [] 327 | parameters: {} 328 | responses: {} 329 | securitydefinitions: {} 330 | tags: [] 331 | externaldocs: null 332 | --------------------------------------------------------------------------------