Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5a7d108
start new telemetry with channels
edwardmack May 17, 2021
3d2e5ce
remove old telemetry code
edwardmack May 18, 2021
e09a762
add tests
edwardmack May 18, 2021
64f9a54
Merge branch 'development' into ed/tel_channelRefactor
edwardmack May 18, 2021
8791a42
Merge branch 'development' into ed/tel_channelRefactor
edwardmack May 18, 2021
94b555c
lint
edwardmack May 18, 2021
a8bbce2
go fmt
edwardmack May 18, 2021
a2c3e01
fix anti-pattern returning unexported types
edwardmack May 18, 2021
416b241
Merge branch 'development' into ed/tel_channelRefactor
edwardmack May 19, 2021
5b0e29c
Merge branch 'development' into ed/tel_channelRefactor
edwardmack May 25, 2021
2389682
added context, send websocket messages is goroutine (broken)
edwardmack May 25, 2021
86841f7
Merge branch 'development' into ed/tel_channelRefactor
edwardmack May 25, 2021
4798c14
move mutex to handler struct
edwardmack May 25, 2021
4a2d693
Merge branch 'development' into ed/tel_channelRefactor
edwardmack May 26, 2021
5d84332
embed mutex inside Handler
edwardmack May 26, 2021
f20eaeb
clean-up formatting to one NewKeyValue per line
edwardmack May 26, 2021
28a5ef8
go fmt
edwardmack May 26, 2021
9e15a35
remove empty body anti-pattern
edwardmack May 26, 2021
14f67ac
go fmt
edwardmack May 26, 2021
8f9d4f1
add test for concurrent connections
edwardmack May 26, 2021
e5690ed
remove context from Handler
edwardmack May 26, 2021
53d8278
move mutex to telemetryConnection struct, make
edwardmack May 27, 2021
cdf1f3d
add logging
edwardmack May 27, 2021
83c05c0
add timeout and error to SendMessage, fix typos, logging
edwardmack May 28, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions dot/network/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,9 +299,10 @@ main:

case <-ticker.C:
o := s.host.bwc.GetBandwidthTotals()
telemetry.GetInstance().SendNetworkData(telemetry.NewNetworkData(s.host.peerCount(), o.RateIn, o.RateOut))
telemetry.GetInstance().SendMessage(telemetry.NewTelemetryMessage(telemetry.NewKeyValue("bandwidth_download", o.RateIn),
telemetry.NewKeyValue("bandwidth_upload", o.RateOut), telemetry.NewKeyValue("msg", "system.interval"),
telemetry.NewKeyValue("peers", s.host.peerCount())))
}

}
}

Expand All @@ -316,14 +317,11 @@ func (s *Service) sentBlockIntervalTelemetry() {
continue
}

telemetry.GetInstance().SendBlockIntervalData(&telemetry.BlockIntervalData{
BestHash: best.Hash(),
BestHeight: best.Number,
FinalizedHash: finalized.Hash(),
FinalizedHeight: finalized.Number,
TXCount: 0, // todo (ed) determine where to get tx count
UsedStateCacheSize: 0, // todo (ed) determine where to get used_state_cache_size
})
telemetry.GetInstance().SendMessage(telemetry.NewTelemetryMessage(telemetry.NewKeyValue("best", best.Hash().String()),
telemetry.NewKeyValue("finalized_hash", finalized.Hash().String()), telemetry.NewKeyValue("finalized_height", finalized.Number), //nolint

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the block here is the best hash, not the best finalized hash

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@noot I'm confused, in there a difference between finalized hash and best finalized hash? How do I obtain finalized hash?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

BlockTree

finalized, err := s.blockState.GetFinalizedHeader(0, 0)

This gives the finalized header.
I think finalized already gives the latest finalized block.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

telemetry.GetInstance().SendMessage(telemetry.NewTelemetryMessage(
	telemetry.NewKeyValue("best", best.Hash().String()),
	telemetry.NewKeyValue("finalized_hash", finalized.Hash().String()),
	telemetry.NewKeyValue("finalized_height", finalized.Number), //nolint
	telemetry.NewKeyValue("height", best.Number), telemetry.NewKeyValue("msg", "system.interval"),
	telemetry.NewKeyValue("txcount", 0),                // todo (ed) determine where to get tx count
	telemetry.NewKeyValue("used_state_cache_size", 0)), // todo (ed) determine where to get used_state_cache_size
)

Also, have a single NewKeyValue per line.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

yes, nevermind, I read this wrong, it's actually fine

telemetry.NewKeyValue("height", best.Number), telemetry.NewKeyValue("msg", "system.interval"),
telemetry.NewKeyValue("txcount", 0), // todo (ed) determine where to get tx count
telemetry.NewKeyValue("used_state_cache_size", 0))) // todo (ed) determine where to get used_state_cache_size
time.Sleep(s.telemetryInterval)
}
}
Expand Down
17 changes: 6 additions & 11 deletions dot/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,17 +352,12 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore, stopFunc func()) (*Node,
}

telemetry.GetInstance().AddConnections(gd.TelemetryEndpoints)
data := &telemetry.ConnectionData{
Authority: cfg.Core.GrandpaAuthority,
Chain: sysSrvc.ChainName(),
GenesisHash: stateSrvc.Block.GenesisHash().String(),
SystemName: sysSrvc.SystemName(),
NodeName: cfg.Global.Name,
SystemVersion: sysSrvc.SystemVersion(),
NetworkID: networkSrvc.NetworkState().PeerID,
StartTime: strconv.FormatInt(time.Now().UnixNano(), 10),
}
telemetry.GetInstance().SendConnection(data)

telemetry.GetInstance().SendMessage(telemetry.NewTelemetryMessage(telemetry.NewKeyValue("authority", cfg.Core.GrandpaAuthority),
telemetry.NewKeyValue("chain", sysSrvc.ChainName()), telemetry.NewKeyValue("genesis_hash", stateSrvc.Block.GenesisHash().String()),
telemetry.NewKeyValue("implementation", sysSrvc.SystemName()), telemetry.NewKeyValue("msg", "system.connected"),
telemetry.NewKeyValue("name", cfg.Global.Name), telemetry.NewKeyValue("network_id", networkSrvc.NetworkState().PeerID),
telemetry.NewKeyValue("startup_time", strconv.FormatInt(time.Now().UnixNano(), 10)), telemetry.NewKeyValue("version", sysSrvc.SystemVersion())))

return node, nil
}
Expand Down
5 changes: 4 additions & 1 deletion dot/sync/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,10 @@ func (s *Service) handleBlock(block *types.Block) error {
}
} else {
logger.Debug("🔗 imported block", "number", block.Header.Number, "hash", block.Header.Hash())
telemetry.GetInstance().SendBlockImport(block.Header.Hash().String(), block.Header.Number)
telemetry.GetInstance().SendMessage(telemetry.NewTelemetryMessage((telemetry.NewKeyValue("best", block.Header.Hash().String())),
telemetry.NewKeyValue("height", block.Header.Number.Uint64()), telemetry.NewKeyValue("msg", "block.import"),
telemetry.NewKeyValue("origin", "NetworkInitialSync")))
// todo(ed) add timer to avoid a lot of sends

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

does this cause a problem?

@noot noot May 27, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

also, is block.import used for all new builds (ie ones we built as well?) if so this might be better placed in BlockState.AddBlock. however if it's strictly imported blocks this is fine

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point, it hasn't yet.

}

// handle consensus digest for authority changes
Expand Down
178 changes: 65 additions & 113 deletions dot/telemetry/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,35 @@
package telemetry

import (
"bytes"
"encoding/json"
"fmt"
"math/big"
"sync"
"time"

"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/lib/genesis"
"github.com/gorilla/websocket"
log "github.com/sirupsen/logrus"
)

// Handler struct for holding telemetry related things
type Handler struct {
buf bytes.Buffer
wsConn []*websocket.Conn
sync.RWMutex
type telemetryConnection struct {
wsconn *websocket.Conn
verbosity int
}

// MyJSONFormatter struct for defining JSON Formatter
type MyJSONFormatter struct {
// Message struct to hold telemetry message data
type Message struct {
values map[string]interface{}
}

// Format function for handling JSON formatting, this overrides default logging formatter to remove
// log level, line number and timestamp
func (f *MyJSONFormatter) Format(entry *log.Entry) ([]byte, error) {
serialised, err := json.Marshal(entry.Data)
if err != nil {
return nil, fmt.Errorf("failed to marshal fields to JSON, %w", err)
}
return append(serialised, '\n'), nil
// Handler struct for holding telemetry related things
type Handler struct {
msg chan Message
connections []telemetryConnection
}

// KeyValue object to hold key value pairs used in telemetry messages
type KeyValue struct {
key string
value interface{}
}

var (
Expand All @@ -57,126 +54,81 @@ var (
)

// GetInstance singleton pattern to for accessing TelemetryHandler
func GetInstance() *Handler {
func GetInstance() *Handler { //nolint
if handlerInstance == nil {
once.Do(
func() {
handlerInstance = &Handler{
buf: bytes.Buffer{},
msg: make(chan Message, 10),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Increase the buffer size to 256.
It may slow down the node if the buffer is full since SendMessage is called form handleBlock

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updateded to 256.

}
log.SetOutput(&handlerInstance.buf)
log.SetFormatter(new(MyJSONFormatter))
go handlerInstance.sender()
go handlerInstance.startListening()
})
}
return handlerInstance
}

// AddConnections adds connections to telemetry sever
func (h *Handler) AddConnections(conns []*genesis.TelemetryEndpoint) {
// NewTelemetryMessage builds a telemetry message
func NewTelemetryMessage(values ...KeyValue) *Message { //nolint
mvals := make(map[string]interface{})
for _, v := range values {
mvals[v.key] = v.value
}
return &Message{
values: mvals,
}
}

// NewKeyValue builds a key value pair for telemetry messages
func NewKeyValue(key string, value interface{}) KeyValue { //nolint

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Make this function return pointer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Made function return pointer.

return KeyValue{
key: key,
value: value,
}
}

// AddConnections adds the given telemetry endpoint as listeners that will receive telemetry data
func (t *Handler) AddConnections(conns []*genesis.TelemetryEndpoint) {
for _, v := range conns {
c, _, err := websocket.DefaultDialer.Dial(v.Endpoint, nil)
if err != nil {
// todo (ed) try reconnecting if there is an error connecting
fmt.Printf("Error %v\n", err)
continue
}
h.wsConn = append(h.wsConn, c)
}
}

// ConnectionData struct to hold connection data
type ConnectionData struct {
Authority bool
Chain string
GenesisHash string
SystemName string
NodeName string
SystemVersion string
NetworkID string
StartTime string
}

// SendConnection sends connection request message to telemetry connection
func (h *Handler) SendConnection(data *ConnectionData) {
h.Lock()
defer h.Unlock()
payload := log.Fields{"authority": data.Authority, "chain": data.Chain, "config": "", "genesis_hash": data.GenesisHash,
"implementation": data.SystemName, "msg": "system.connected", "name": data.NodeName, "network_id": data.NetworkID, "startup_time": data.StartTime,
"version": data.SystemVersion}
telemetryLogger := log.WithFields(log.Fields{"id": 1, "payload": payload, "ts": time.Now()})
telemetryLogger.Print()
}

// SendBlockImport sends block imported message to telemetry connection
func (h *Handler) SendBlockImport(bestHash string, height *big.Int) {
h.Lock()
defer h.Unlock()
payload := log.Fields{"best": bestHash, "height": height.Int64(), "msg": "block.import", "origin": "NetworkInitialSync"}
telemetryLogger := log.WithFields(log.Fields{"id": 1, "payload": payload, "ts": time.Now()})
telemetryLogger.Print()
}

// NetworkData struct to hold network data telemetry information
type NetworkData struct {
peers int
rateIn float64
rateOut float64
}

// NewNetworkData creates networkData struct
func NewNetworkData(peers int, rateIn, rateOut float64) *NetworkData {
return &NetworkData{
peers: peers,
rateIn: rateIn,
rateOut: rateOut,
tConn := telemetryConnection{
wsconn: c,
verbosity: v.Verbosity,
}
t.connections = append(t.connections, tConn)
}
}

// SendNetworkData send network data system.interval message to telemetry connection
func (h *Handler) SendNetworkData(data *NetworkData) {
h.Lock()
defer h.Unlock()
payload := log.Fields{"bandwidth_download": data.rateIn, "bandwidth_upload": data.rateOut, "msg": "system.interval", "peers": data.peers}
telemetryLogger := log.WithFields(log.Fields{"id": 1, "payload": payload, "ts": time.Now()})
telemetryLogger.Print()
// SendMessage sends Message to connected telemetry listeners
func (t *Handler) SendMessage(msg *Message) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
func (t *Handler) SendMessage(msg *Message) {
func (h *Handler) SendMessage(msg *Message) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated.

t.msg <- *msg

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

improvement: add a timeout and error return for this method. I see that that it's a buffered channel of length 256, so this shouldn't happen, but it's a good practice so that this function call isn't blocking.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good suggestion, I've added timeout and return error.

}

// BlockIntervalData struct to hold data for block system.interval message
type BlockIntervalData struct {
BestHash common.Hash
BestHeight *big.Int
FinalizedHash common.Hash
FinalizedHeight *big.Int
TXCount int
UsedStateCacheSize int
}

// SendBlockIntervalData send block data system interval information to telemetry connection
func (h *Handler) SendBlockIntervalData(data *BlockIntervalData) {
h.Lock()
defer h.Unlock()
payload := log.Fields{"best": data.BestHash.String(), "finalized_hash": data.FinalizedHash.String(), // nolint
"finalized_height": data.FinalizedHeight, "height": data.BestHeight, "msg": "system.interval", "txcount": data.TXCount, // nolint
"used_state_cache_size": data.UsedStateCacheSize}
telemetryLogger := log.WithFields(log.Fields{"id": 1, "payload": payload, "ts": time.Now()})
telemetryLogger.Print()
}

func (h *Handler) sender() {
func (t *Handler) startListening() {
for {
h.RLock()
line, err := h.buf.ReadBytes(byte(10)) // byte 10 is newline character, used as delimiter
h.RUnlock()
if err != nil {
continue
}

for _, c := range h.wsConn {
err := c.WriteMessage(websocket.TextMessage, line)
msg := <-t.msg

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In this case, is possible to have a context on Handler struct? like:

select {
case msg := <-t.msg: 
  // exec the logic here when a msg arrives

case <-t.ctx.Done():
  //break the loop
  break
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

agree, return if <-t.ctx.Done()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

A added context to the Handler struct.

for _, v := range t.connections {
err := v.wsconn.WriteMessage(websocket.TextMessage, msgToBytes(msg))
if err != nil {
// TODO (ed) determine how to handle this error
fmt.Printf("ERROR connecting to telemetry %v\n", err)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can you ignore this error for now - I sometimes see a lot of logs of this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed printf.

}

@noot noot May 19, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe put this in a goroutine so that reading/writing to/from t.msg doesn't block?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I tried putting this in a go routine, however then I was getting panic: concurrent write to websocket connection. So then I tried protecting websocket connection with a mutex, but now the linter complains copylocks: range var v copies lock: github.com/ChainSafe/gossamer/dot/telemetry.telemetryConnection contains sync.Mutex (govet) and it still panics with concurrent write to websocket. So, I must be handling this in-correctly, any suggestions? @noot, @arijitAD, @timwu20, @EclesioMeloJunior

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Disregard above comment, I've moved mutex to Handler struct, where it makes more sense.

}
}

func msgToBytes(message Message) []byte {
res := make(map[string]interface{})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe create a helper struct to construct the json response.

type response struct {
  ID int `json:"id"`
  ....
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good idea, created.

res["id"] = 1 // todo (ed) determine how this is used
res["payload"] = message.values
res["ts"] = time.Now()
resB, err := json.Marshal(res)
if err != nil {
return nil
}
return resB
}
44 changes: 20 additions & 24 deletions dot/telemetry/telemetry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import (
"testing"
"time"

"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/lib/genesis"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/require"
)

var upgrader = websocket.Upgrader{}
var resultCh chan []byte

func TestMain(m *testing.M) {
// start server to listen for websocket connections
Expand All @@ -39,54 +39,50 @@ func TestMain(m *testing.M) {
os.Exit(code)
}

var resultCh chan []byte

func TestHandler_SendMulti(t *testing.T) {
var wg sync.WaitGroup
wg.Add(4)

resultCh = make(chan []byte)

go func() {
GetInstance().SendConnection(&ConnectionData{
Authority: false,
Chain: "chain",
GenesisHash: "hash",
SystemName: "systemName",
NodeName: "nodeName",
SystemVersion: "version",
NetworkID: "netID",
StartTime: "startTime",
})
GetInstance().SendMessage(NewTelemetryMessage(NewKeyValue("authority", false),
NewKeyValue("chain", "chain"), NewKeyValue("genesis_hash", "hash"),
NewKeyValue("implementation", "systemName"), NewKeyValue("msg", "system.connected"),
NewKeyValue("name", "nodeName"), NewKeyValue("network_id", "netID"),
NewKeyValue("startup_time", "startTime"), NewKeyValue("version", "version")))

wg.Done()
}()

go func() {
GetInstance().SendBlockImport("hash", big.NewInt(2))
GetInstance().SendMessage(NewTelemetryMessage((NewKeyValue("best", "hash")),
NewKeyValue("height", big.NewInt(2)), NewKeyValue("msg", "block.import"),
NewKeyValue("origin", "NetworkInitialSync")))
wg.Done()
}()

go func() {
GetInstance().SendNetworkData(NewNetworkData(1, 2, 3))
GetInstance().SendMessage(NewTelemetryMessage(NewKeyValue("bandwidth_download", 2),
NewKeyValue("bandwidth_upload", 3), NewKeyValue("msg", "system.interval"),
NewKeyValue("peers", 1)))
wg.Done()
}()

go func() {
GetInstance().SendBlockIntervalData(&BlockIntervalData{
BestHash: common.MustHexToHash("0x07b749b6e20fd5f1159153a2e790235018621dd06072a62bcd25e8576f6ff5e6"),
BestHeight: big.NewInt(32375),
FinalizedHash: common.MustHexToHash("0x687197c11b4cf95374159843e7f46fbcd63558db981aaef01a8bac2a44a1d6b2"),
FinalizedHeight: big.NewInt(32256),
TXCount: 2,
UsedStateCacheSize: 1886357,
})
GetInstance().SendMessage(NewTelemetryMessage(NewKeyValue("best", "0x07b749b6e20fd5f1159153a2e790235018621dd06072a62bcd25e8576f6ff5e6"),
NewKeyValue("finalized_hash", "0x687197c11b4cf95374159843e7f46fbcd63558db981aaef01a8bac2a44a1d6b2"), // nolint
NewKeyValue("finalized_height", 32256), NewKeyValue("height", 32375), // nolint
NewKeyValue("msg", "system.interval"), NewKeyValue("txcount", 2),
NewKeyValue("used_state_cache_size", 1886357)))
wg.Done()
}()

wg.Wait()

expected1 := []byte(`{"id":1,"payload":{"bandwidth_download":2,"bandwidth_upload":3,"msg":"system.interval","peers":1},"ts":`)
expected2 := []byte(`{"id":1,"payload":{"best":"hash","height":2,"msg":"block.import","origin":"NetworkInitialSync"},"ts":`)
expected3 := []byte(`{"id":1,"payload":{"authority":false,"chain":"chain","config":"","genesis_hash":"hash","implementation":"systemName","msg":"system.connected","name":"nodeName","network_id":"netID","startup_time":"startTime","version":"version"},"ts":`)
expected3 := []byte(`{"id":1,"payload":{"authority":false,"chain":"chain","genesis_hash":"hash","implementation":"systemName","msg":"system.connected","name":"nodeName","network_id":"netID","startup_time":"startTime","version":"version"},"ts":`)
expected4 := []byte(`{"id":1,"payload":{"best":"0x07b749b6e20fd5f1159153a2e790235018621dd06072a62bcd25e8576f6ff5e6","finalized_hash":"0x687197c11b4cf95374159843e7f46fbcd63558db981aaef01a8bac2a44a1d6b2","finalized_height":32256,"height":32375,"msg":"system.interval","txcount":2,"used_state_cache_size":1886357},"ts":`) // nolint

expected := [][]byte{expected3, expected1, expected4, expected2}
Expand Down