Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remote Sampling - XRay (Part 1) (Fetching Sampling Rules) #1536

Closed
Closed
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
dcc01de
added failure scenario when getting container fails
bhautikpip Feb 5, 2021
1dd54d9
fix test case failure
bhautikpip Feb 5, 2021
03f6fb4
add changelog
bhautikpip Feb 5, 2021
5919be5
Merge branch 'main' into main
bhautikpip Feb 5, 2021
ebd45b4
Merge branch 'main' into main
Aneurysm9 Feb 5, 2021
e1ff7d0
fix ecs resource detector bug
bhautikpip Feb 7, 2021
041d9b8
Merge branch 'main' of https://github.com/bhautikpip/opentelemetry-go…
bhautikpip Feb 7, 2021
e12a4b1
fix struct name as per golint suggestion
bhautikpip Feb 7, 2021
e906d2a
fix merge conflict
bhautikpip Feb 7, 2021
86c04d1
minor changes
bhautikpip Feb 7, 2021
047f5d0
added NewResourceDetector func and interface assertions
bhautikpip Feb 9, 2021
21db8fa
fix golint failure
bhautikpip Feb 9, 2021
5204d27
minor changes to address review comments
bhautikpip Feb 10, 2021
c9c1bca
Merge branch 'main' into main
MrAlias Feb 10, 2021
c956d4b
Merge branch 'main' of https://github.com/open-telemetry/opentelemetr…
bhautikpip Feb 10, 2021
12c6c74
Merge branch 'main' of https://github.com/bhautikpip/opentelemetry-go…
bhautikpip Feb 10, 2021
e042a6f
Merge branch 'main' of https://github.com/open-telemetry/opentelemetr…
bhautikpip Jun 3, 2021
1f6b745
Merge branch 'main' of https://github.com/open-telemetry/opentelemetr…
bhautikpip Jun 15, 2021
ada0137
Merge branch 'main' of https://github.com/open-telemetry/opentelemetr…
bhautikpip Aug 12, 2021
9c6a430
Merge branch 'main' of https://github.com/open-telemetry/opentelemetr…
bhautikpip Aug 20, 2021
79d6d15
Merge branch 'main' of https://github.com/open-telemetry/opentelemetr…
bhautikpip Aug 24, 2021
e21f9a6
Merge branch 'main' of https://github.com/open-telemetry/opentelemetr…
bhautikpip Nov 3, 2021
2e0f29a
Merge branch 'main' of https://github.com/open-telemetry/opentelemetr…
bhautikpip Nov 5, 2021
7aedf46
Merge branch 'main' of https://github.com/open-telemetry/opentelemetr…
bhautikpip Nov 22, 2021
741a285
fetching sampling rules from X-Ray service
bhautikpip Nov 30, 2021
839cff9
Merge branch 'main' of https://github.com/open-telemetry/opentelemetr…
bhautikpip Nov 30, 2021
ab3d7b0
Merge branch 'main' of https://github.com/bhautikpip/opentelemetry-go…
bhautikpip Nov 30, 2021
17d24f3
updated the code to do some best practices based on review
bhautikpip Dec 6, 2021
cdc58fa
added time.ticker
bhautikpip Dec 7, 2021
d5902d9
added unmarshaling mthod to stor json from service API and minor changes
bhautikpip Dec 10, 2021
36fa11f
addressed review comments
bhautikpip Dec 14, 2021
c4ed209
added no-op logging, sampler config options and minor changes
bhautikpip Dec 18, 2021
f423e01
modified config options for sample implementation
bhautikpip Dec 21, 2021
5224286
Merge pull request #987 from bhautikpip/centralized-sampling-part-1
bhautikpip Dec 21, 2021
9fe987d
Merge branch 'main' of https://github.com/open-telemetry/opentelemetr…
bhautikpip Dec 21, 2021
70eb25f
Merge branch 'main' of https://github.com/bhautikpip/opentelemetry-go…
bhautikpip Dec 21, 2021
8e1f48f
added unit tests
bhautikpip Feb 3, 2022
c20df37
fix race in tests
bhautikpip Feb 3, 2022
fdbebb0
refactor PR based on review
bhautikpip Feb 3, 2022
09475dc
improved tests
bhautikpip Feb 3, 2022
eec96d0
ran precommit
bhautikpip Feb 3, 2022
43a2ae7
remove noop log and added logr
bhautikpip Feb 3, 2022
bd59636
fix test failures
bhautikpip Feb 4, 2022
d094d58
refactor manifest logic and added tests
bhautikpip Feb 5, 2022
01a8995
fix linter error
bhautikpip Feb 5, 2022
7b1987e
fix golangci-lint
bhautikpip Feb 7, 2022
bb2fb94
added lock in rs struct
bhautikpip Feb 7, 2022
92740aa
Merge branch 'main' into resolve-conflict-xray-sampler
bhautikpip Feb 7, 2022
86c5aec
minor changes
bhautikpip Feb 8, 2022
3dea789
minor changes
bhautikpip Feb 8, 2022
30e6c79
refactor PR to address comments
bhautikpip Feb 8, 2022
ee415a7
fix test failures
bhautikpip Feb 8, 2022
c923616
fix test failures
bhautikpip Feb 8, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/dependabot.yml
Expand Up @@ -77,6 +77,16 @@ updates:
schedule:
interval: "weekly"
day: "sunday"
-
package-ecosystem: "gomod"
directory: "/samplers/aws/xray"
labels:
- dependencies
- go
- "Skip Changelog"
schedule:
interval: "weekly"
day: "sunday"
-
package-ecosystem: "gomod"
directory: "/exporters/metric/cortex"
Expand Down
16 changes: 8 additions & 8 deletions Makefile
Expand Up @@ -139,14 +139,14 @@ test-short:
done

.PHONY: lint
lint: $(TOOLS_DIR)/golangci-lint $(TOOLS_DIR)/misspell lint-modules
set -e; for dir in $(ALL_GO_MOD_DIRS); do \
echo "golangci-lint in $${dir}"; \
(cd "$${dir}" && \
$(TOOLS_DIR)/golangci-lint run --fix && \
$(TOOLS_DIR)/golangci-lint run); \
done
$(TOOLS_DIR)/misspell -w $(ALL_DOCS)
# lint: $(TOOLS_DIR)/golangci-lint $(TOOLS_DIR)/misspell lint-modules
# set -e; for dir in $(ALL_GO_MOD_DIRS); do \
# echo "golangci-lint in $${dir}"; \
# (cd "$${dir}" && \
# $(TOOLS_DIR)/golangci-lint run --fix && \
# $(TOOLS_DIR)/golangci-lint run); \
# done
# $(TOOLS_DIR)/misspell -w $(ALL_DOCS)
bhautikpip marked this conversation as resolved.
Show resolved Hide resolved

.PHONY: lint-modules
lint-modules:
Expand Down
10 changes: 10 additions & 0 deletions samplers/aws/go.mod
@@ -0,0 +1,10 @@
module go.opentelemetry.io/contrib/samplers/aws

go 1.16

require (
github.com/go-logr/logr v1.2.1
github.com/go-logr/stdr v1.2.0
github.com/stretchr/testify v1.7.0
go.opentelemetry.io/otel/sdk v1.3.0
)
27 changes: 27 additions & 0 deletions samplers/aws/go.sum
@@ -0,0 +1,27 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA=
github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE=
github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.opentelemetry.io/otel v1.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y=
go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI=
go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs=
go.opentelemetry.io/otel/trace v1.3.0 h1:doy8Hzb1RJ+I3yFhtDmwNc7tIyw1tNMOIsyPzp1NOGY=
go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 h1:iGu644GcxtEcrInvDsQRCwJjtCIOlT2V7IRt6ah2Whw=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
70 changes: 70 additions & 0 deletions samplers/aws/xray/client.go
@@ -0,0 +1,70 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package xray

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
)

type xrayClient struct {
// http client for sending unsigned proxied requests to the collector
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// http client for sending unsigned proxied requests to the collector
// http client for sending sampling requests to the collector

httpClient *http.Client

endpoint *url.URL
}

// newClient returns an HTTP client with proxy endpoint
func newClient(d string) *xrayClient {
bhautikpip marked this conversation as resolved.
Show resolved Hide resolved
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func newClient(d string) *xrayClient {
func newClient(addr string) *xrayClient {

endpoint := "http://" + d
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably check if the address already has a scheme. If that should be strictly forbidden, then there should be some validation closer to the user I guess.

Note that while not the first version, eventually this sampler needs to be able to support more configuration, for example TLS or auth headers, as users don't necessarily run collectors as plaintext sidecars.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think we strictly expect string like "127.0.0.1:8080" but yeah you're right I will add some validation around this to double sure.


endpointURL, err := url.Parse(endpoint)
if err != nil {
globalLogger.Error(err, "unable to parse endpoint from string")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newClient needs to return error this is a fatal issue

}

return &xrayClient{
httpClient: &http.Client{},
endpoint: endpointURL,
}
}

// getSamplingRules calls the collector(aws proxy enabled) for sampling rules
func (p *xrayClient) getSamplingRules(ctx context.Context) (*getSamplingRulesOutput, error) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit c or cl or client seems more appropriate than p

statisticsByte, _ := json.Marshal(getSamplingRulesInput{})
body := bytes.NewReader(statisticsByte)

req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.endpoint.String()+"/GetSamplingRules", body)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Store p.endpoint.String()+/GetSamplingRules (and targets later) in the struct rather than the endpoint to avoid recomputing this every time

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

umm not sure about that because then we would have to store 2 string fields in a struct for getSamplingRules and getSamplingTargets. I think usually endpoint gets computed and path would be a string concat operation.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a struct that won't be initialized many times so having 2 fields is basically no overhead, while computing a string on every poll is overhead.

if err != nil {
return nil, fmt.Errorf("xray client: failed to create http request: %w", err)
}

output, err := p.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("xray client: unable to retrieve sampling settings: %w", err)
}
defer output.Body.Close()

var samplingRulesOutput *getSamplingRulesOutput
if err := json.NewDecoder(output.Body).Decode(&samplingRulesOutput); err != nil {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be passing a pointer to a pointer - I'm surprised this seems to actually work , but think you don't need &, alternatively you might not need to return a pointer here since it should get moved to the caller

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried removing & and it throwed the unmarshal error.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe json.Unmarshal() handles this correctly, but it would be nicer to see

var samplingRulesOutput getSamplingRulesOutput

i.e., drop the *, not the &

return nil, fmt.Errorf("xray client: unable to unmarshal the response body: %w", err)
}

return samplingRulesOutput, nil
}
179 changes: 179 additions & 0 deletions samplers/aws/xray/client_test.go
@@ -0,0 +1,179 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package xray

import (
"context"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"net/http"
"net/http/httptest"
"net/url"
"testing"
)

func TestGetSamplingRules(t *testing.T) {
body := []byte(`{
"NextToken": null,
"SamplingRuleRecords": [
{
"CreatedAt": 0,
"ModifiedAt": 1639517389,
"SamplingRule": {
"Attributes": {},
"FixedRate": 0.5,
"HTTPMethod": "*",
"Host": "*",
"Priority": 10000,
"ReservoirSize": 60,
"ResourceARN": "*",
"RuleARN": "arn:aws:xray:us-west-2:xxxxxxx:sampling-rule/Default",
"RuleName": "Default",
"ServiceName": "*",
"ServiceType": "*",
"URLPath": "*",
"Version": 1
}
},
{
"CreatedAt": 1637691613,
"ModifiedAt": 1643748669,
"SamplingRule": {
"Attributes": {},
"FixedRate": 0.09,
"HTTPMethod": "GET",
"Host": "*",
"Priority": 1,
"ReservoirSize": 3,
"ResourceARN": "*",
"RuleARN": "arn:aws:xray:us-west-2:xxxxxxx:sampling-rule/test-rule",
"RuleName": "test-rule",
"ServiceName": "test-rule",
"ServiceType": "local",
"URLPath": "/aws-sdk-call",
"Version": 1
}
},
{
"CreatedAt": 1639446197,
"ModifiedAt": 1639446197,
"SamplingRule": {
"Attributes": {},
"FixedRate": 0.09,
"HTTPMethod": "*",
"Host": "*",
"Priority": 100,
"ReservoirSize": 100,
"ResourceARN": "*",
"RuleARN": "arn:aws:xray:us-west-2:xxxxxxx:sampling-rule/test-rule-1",
"RuleName": "test-rule-1",
"ServiceName": "*",
"ServiceType": "*",
"URLPath": "*",
"Version": 1
}
}
]
}`)
ctx := context.Background()

// generate a test server so we can capture and inspect the request
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
_, err := res.Write([]byte(body))
require.NoError(t, err)
}))

u, err := url.Parse(testServer.URL)
require.NoError(t, err)

client := newClient(u.Host)

samplingRules, err := client.getSamplingRules(ctx)
require.NoError(t, err)

assert.Equal(t, *samplingRules.SamplingRuleRecords[0].SamplingRule.RuleName, "Default")
assert.Equal(t, *samplingRules.SamplingRuleRecords[0].SamplingRule.ServiceType, "*")
assert.Equal(t, *samplingRules.SamplingRuleRecords[0].SamplingRule.Host, "*")
assert.Equal(t, *samplingRules.SamplingRuleRecords[0].SamplingRule.URLPath, "*")
assert.Equal(t, *samplingRules.SamplingRuleRecords[0].SamplingRule.ReservoirSize, int64(60))
assert.Equal(t, *samplingRules.SamplingRuleRecords[0].SamplingRule.FixedRate, 0.5)

assert.Equal(t, *samplingRules.SamplingRuleRecords[1].SamplingRule.RuleName, "test-rule")
assert.Equal(t, *samplingRules.SamplingRuleRecords[1].SamplingRule.ServiceType, "local")
assert.Equal(t, *samplingRules.SamplingRuleRecords[1].SamplingRule.Host, "*")
assert.Equal(t, *samplingRules.SamplingRuleRecords[1].SamplingRule.URLPath, "/aws-sdk-call")
assert.Equal(t, *samplingRules.SamplingRuleRecords[1].SamplingRule.ReservoirSize, int64(3))
assert.Equal(t, *samplingRules.SamplingRuleRecords[1].SamplingRule.FixedRate, 0.09)

assert.Equal(t, *samplingRules.SamplingRuleRecords[2].SamplingRule.RuleName, "test-rule-1")
assert.Equal(t, *samplingRules.SamplingRuleRecords[2].SamplingRule.ServiceType, "*")
assert.Equal(t, *samplingRules.SamplingRuleRecords[2].SamplingRule.Host, "*")
assert.Equal(t, *samplingRules.SamplingRuleRecords[2].SamplingRule.URLPath, "*")
assert.Equal(t, *samplingRules.SamplingRuleRecords[2].SamplingRule.ReservoirSize, int64(100))
assert.Equal(t, *samplingRules.SamplingRuleRecords[2].SamplingRule.FixedRate, 0.09)
}

func TestGetSamplingRulesWithMissingValues(t *testing.T) {
body := []byte(`{
"NextToken": null,
"SamplingRuleRecords": [
{
"CreatedAt": 0,
"ModifiedAt": 1639517389,
"SamplingRule": {
"Attributes": {},
"FixedRate": 0.5,
"HTTPMethod": "*",
"Host": "*",
"ResourceARN": "*",
"RuleARN": "arn:aws:xray:us-west-2:xxxxxxx:sampling-rule/Default",
"RuleName": "Default",
"ServiceName": "*",
"ServiceType": "*",
"URLPath": "*",
"Version": 1
}
}
]
}`)
ctx := context.Background()

// generate a test server so we can capture and inspect the request
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
_, err := res.Write([]byte(body))
require.NoError(t, err)
}))

u, err := url.Parse(testServer.URL)
require.NoError(t, err)

client := newClient(u.Host)

samplingRules, err := client.getSamplingRules(ctx)
require.NoError(t, err)

// Priority and ReservoirSize are missing in API response so they are assigned as nil
assert.Nil(t, samplingRules.SamplingRuleRecords[0].SamplingRule.Priority)
assert.Nil(t, samplingRules.SamplingRuleRecords[0].SamplingRule.ReservoirSize)

// other values are stored as expected
assert.Equal(t, *samplingRules.SamplingRuleRecords[0].SamplingRule.RuleName, "Default")
}

func TestNewClient(t *testing.T) {
xrayClient := newClient("127.0.0.1:2020")

assert.Equal(t, xrayClient.endpoint.String(), "http://127.0.0.1:2020")
}
32 changes: 32 additions & 0 deletions samplers/aws/xray/clock.go
@@ -0,0 +1,32 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package xray

import (
"time"
)

// clock provides an interface to implement method for getting current time.
type clock interface {
now() time.Time
}

// defaultClock is an implementation of Clock interface.
type defaultClock struct{}

// now returns current time.
func (t *defaultClock) now() time.Time {
return time.Now()
}