Commit edc10ee7 authored by Qianwen's avatar Qianwen
Browse files

fix error

parent b292dd0d
Pipeline #486929 passed with stages
in 3 minutes and 20 seconds
......@@ -88,7 +88,7 @@ func task_test(ag *agency.Agent) (err error) {
// agent 1 publishes the topic1
if id == 1 {
for i := 0; i < 10; i++ {
for i := 0; i < 20; i++ {
time.Sleep(5 * time.Second)
msg := "test message" + strconv.Itoa(i)
MQTTMsg, err := ag.MQTT.NewMessage("topic1", []byte(msg))
......
......@@ -93,7 +93,7 @@ func (agent *Agent) NewMessageBehavior(protocol int,
// Start initiates the handling of messages
func (protBehavior *aclProtocolBehavior) Start() {
// log the start
protBehavior.ag.Logger.NewLog("beh", "protocol start", "")
protBehavior.ag.Logger.NewLog("beh", "protocol behavior starts", "")
// register protocol handler
protBehavior.ag.ACL.registerProtocolChannel(protBehavior.protocol, protBehavior.msgIn)
// execute
......@@ -138,7 +138,7 @@ func (protBehavior *aclProtocolBehavior) task() {
// Stop terminates the message handling
func (protBehavior *aclProtocolBehavior) Stop() {
// log the stop
protBehavior.ag.Logger.NewLog("beh", "protocol stop", "")
protBehavior.ag.Logger.NewLog("beh", "protocol behavior stops", "")
// deregister handler
protBehavior.ag.ACL.deregisterProtocolChannel(protBehavior.protocol)
// stop message handling
......@@ -177,7 +177,7 @@ func (agent *Agent) NewMQTTTopicBehavior(topic string,
// Start initiates the handling of messages
func (mqttBehavior *mqttTopicBehavior) Start() {
// log the start
mqttBehavior.ag.Logger.NewLog("beh", "mqtt behavior starts; Topic:"+mqttBehavior.topic, "")
mqttBehavior.ag.Logger.NewLog("beh", "mqtt topic behavior starts; Topic:"+mqttBehavior.topic, "")
// register protocol handle
mqttBehavior.ag.MQTT.registerTopicChannel(mqttBehavior.topic, mqttBehavior.msgIn)
// execute
......@@ -216,7 +216,7 @@ func (mqttBehavior *mqttTopicBehavior) task() {
// Stop terminates the message handling
func (mqttBehavior *mqttTopicBehavior) Stop() {
// log the stop
mqttBehavior.ag.Logger.NewLog("beh", "mqtt stop", "")
mqttBehavior.ag.Logger.NewLog("beh", "mqtt topic behavior stops", "")
// deregister handler
mqttBehavior.ag.MQTT.deregisterTopicChannel(mqttBehavior.topic)
// stop message handling
......@@ -253,14 +253,14 @@ func (agent *Agent) NewPeriodicBehavior(period time.Duration,
// Start initiates the handling of messages
func (periodBehavior *periodicBehavior) Start() {
// log the start
periodBehavior.ag.Logger.NewLog("beh", "period-start", "")
periodBehavior.ag.Logger.NewLog("beh", "periodic behavior starts", "")
// execute
go periodBehavior.task()
}
// task performs the execution of the handle function
func (periodBehavior *periodicBehavior) task() {
periodBehavior.logInfo.Println("Starting periodoc behavior for agent ",
periodBehavior.logInfo.Println("Starting periodic behavior for agent ",
periodBehavior.ag.GetAgentID(), " and period ", periodBehavior.period)
for {
periodBehavior.ag.mutex.Lock()
......@@ -274,7 +274,7 @@ func (periodBehavior *periodicBehavior) task() {
case command := <-periodBehavior.ctrl:
switch command {
case -1:
periodBehavior.logInfo.Println("Terminating periodoc behavior for agent ",
periodBehavior.logInfo.Println("Terminating periodic behavior for agent ",
periodBehavior.ag.GetAgentID())
return
}
......@@ -282,7 +282,7 @@ func (periodBehavior *periodicBehavior) task() {
start := time.Now()
periodBehavior.handle()
end := time.Now()
periodBehavior.ag.Logger.NewBehStats(start, end, "protocol")
periodBehavior.ag.Logger.NewBehStats(start, end, "period")
}
}
......@@ -291,7 +291,7 @@ func (periodBehavior *periodicBehavior) task() {
// Stop terminates the message handling
func (periodBehavior *periodicBehavior) Stop() {
// log the stop
periodBehavior.ag.Logger.NewLog("beh", "period-stop", "")
periodBehavior.ag.Logger.NewLog("beh", "periodic behavior stops", "")
// stop message handling
periodBehavior.ctrl <- -1
}
......@@ -327,7 +327,7 @@ func (agent *Agent) NewCustomUpdateBehavior(
// Start initiates the handling of messages
func (custUpBehavior *customUpdateBehavior) Start() {
// log the start
custUpBehavior.ag.Logger.NewLog("beh", "custom", "start")
custUpBehavior.ag.Logger.NewLog("beh", "custom update behavior starts", "")
custUpBehavior.ag.registerCustomUpdateChannel(custUpBehavior.customIn)
// execute
go custUpBehavior.task()
......@@ -364,7 +364,7 @@ func (custUpBehavior *customUpdateBehavior) task() {
// Stop terminates the behavior
func (custUpBehavior *customUpdateBehavior) Stop() {
// log the stop
custUpBehavior.ag.Logger.NewLog("beh", "custom-stop", "")
custUpBehavior.ag.Logger.NewLog("beh", "custom update behavior ends", "")
custUpBehavior.ag.deregisterCustomUpdateChannel()
// stop behavior
custUpBehavior.ctrl <- -1
......
......@@ -178,15 +178,15 @@ func (cli *LoggerClient) PostBehStats(masID int, logs []schemas.BehStats) (httpS
return
}
// GetMsgHeatMap gets msg communication frequency
func (cli *LoggerClient) GetStats(masID int, agentID int, method string, behtype string, start string, end string) (data float32, httpStatus int, err error) {
// GetStats get the statistical information of agent behavior
func (cli *LoggerClient) GetStats(masID int, agentID int, behtype string, start string, end string) (statsInfo schemas.StatsInfo, httpStatus int, err error) {
var body []byte
body, httpStatus, err = httpretry.Get(cli.httpClient, cli.prefix()+"/api/stats/"+
strconv.Itoa(masID)+"/"+strconv.Itoa(agentID)+"/"+method+"/"+behtype+"/"+start+"/"+end, time.Second*2, 4)
strconv.Itoa(masID)+"/"+strconv.Itoa(agentID)+"/"+behtype+"/"+start+"/"+end, time.Second*2, 4)
if err != nil {
return
}
err = json.Unmarshal(body, &data)
err = json.Unmarshal(body, &statsInfo)
return
}
......
......@@ -205,7 +205,7 @@ func (fe *Frontend) server(port int) (serv *http.Server) {
s.Path("/logging/series/{masid}/{agentid}/names").Methods("GET").HandlerFunc(fe.handleGetLogSeriesNames)
s.Path("/logging/series/{masid}/{agentid}/{name}/time/{start}/{end}").Methods("GET").HandlerFunc(fe.handleGetLogSeriesByName)
s.Path("/logging/stats/{masid}/heatmap").Methods("GET").HandlerFunc(fe.handleGetMsgHeatmap)
s.Path("/logging/stats/{masid}/{agentid}/{method}/{behtype}/{start}/{end}").Methods("GET").HandlerFunc(fe.handleGetStats)
s.Path("/logging/stats/{masid}/{agentid}/{behtype}/{start}/{end}").Methods("GET").HandlerFunc(fe.handleGetStats)
s.Path("/logging/{masid}/{agentid}/{topic}/latest/{num}").Methods("GET").HandlerFunc(fe.handleGetNLatestLogs)
s.Path("/logging/{masid}/list").Methods("POST").HandlerFunc(fe.handlePostLogs)
......
......@@ -235,7 +235,7 @@ func (fe *Frontend) handleGetMsgHeatmap(w http.ResponseWriter, r *http.Request)
return
}
// handleGetStatistics is the handler to /api/logging/stats/{masid}/{agentid}/{method}/{behtype}/{start}/{end}
// handleGetStats is the handler to /api/logging/stats/{masid}/{agentid}/{behtype}/{start}/{end}
func (fe *Frontend) handleGetStats(w http.ResponseWriter, r *http.Request) {
var httpErr, cmapErr error
vars := mux.Vars(r)
......@@ -245,11 +245,10 @@ func (fe *Frontend) handleGetStats(w http.ResponseWriter, r *http.Request) {
fe.logErrors(r.URL.Path, cmapErr, httpErr)
return
}
method := vars["method"]
behtype := vars["behtype"]
start := vars["start"]
end := vars["end"]
data, _, cmapErr := fe.logClient.GetStats(masID, agentID, method, behtype, start, end)
data, _, cmapErr := fe.logClient.GetStats(masID, agentID, behtype, start, end)
if cmapErr != nil {
httpErr = httpreply.CMAPError(w, cmapErr.Error())
fe.logErrors(r.URL.Path, cmapErr, httpErr)
......
......@@ -275,34 +275,25 @@ func (stor *cassStorage) getMsgHeatmap(masID int) (heatmap map[[2]int]int, err e
return
}
// getStatistics get the data of a certain method and topic
func (stor *cassStorage) getStats(masID int, agentID int, method string, behType string, start time.Time, end time.Time) (data float32, err error) {
// getStats get the data of a certain behtype
func (stor *cassStorage) getStats(masID int, agentID int, behType string, start time.Time, end time.Time) (statsInfo schemas.StatsInfo, err error) {
var iter *gocql.Iter
iter = stor.session.Query("SELECT series FROM beh_stats WHERE masid = ? AND agentid = ? AND "+
"behType = ? AND start > ? AND start < ?", masID, agentID, behType, start, end).Iter()
var js []byte
duration := []int{}
for iter.Scan(&js) {
var behStats schemas.BehStats
err = json.Unmarshal(js, &behStats)
if err != nil {
return
}
duration = append(duration, behStats.Duration)
statsInfo.List = append(statsInfo.List, behStats)
}
iter.Close()
switch method {
case "max":
data = float32(getMax(duration))
case "min":
data = float32(getMin(duration))
case "count":
data = float32(len(duration))
case "average":
data = getAverage((duration))
default:
err = errors.New("wrong method")
}
statsInfo.Max = getMax(getDuration(statsInfo.List))
statsInfo.Min = getMin(getDuration(statsInfo.List))
statsInfo.Count = len(statsInfo.List)
statsInfo.Average = getAverage((getDuration(statsInfo.List)))
return
}
......
......@@ -327,7 +327,7 @@ func (logger *Logger) handleGetLogSeriesByName(w http.ResponseWriter, r *http.Re
logger.logErrors(r.URL.Path, cmapErr, httpErr)
}
// handleGetMsgHeatmap is the handler for requests to path /api/statistics/{masid}/heatmap
// handleGetMsgHeatmap is the handler for requests to path /api/stats/{masid}/heatmap
func (logger *Logger) handleGetMsgHeatmap(w http.ResponseWriter, r *http.Request) {
var cmapErr, httpErr error
vars := mux.Vars(r)
......@@ -350,7 +350,7 @@ func (logger *Logger) handleGetMsgHeatmap(w http.ResponseWriter, r *http.Request
logger.logErrors(r.URL.Path, cmapErr, httpErr)
}
// handleGetStatistics is the handler for requests to path /api/statistics/{masid}/{method}/{topic}/{start}/{end}
// handleGetStats is the handler for requests to path /api/statistics/{masid}/{behtype}/{start}/{end}
func (logger *Logger) handleGetStats(w http.ResponseWriter, r *http.Request) {
var cmapErr, httpErr error
vars := mux.Vars(r)
......@@ -358,7 +358,6 @@ func (logger *Logger) handleGetStats(w http.ResponseWriter, r *http.Request) {
if cmapErr != nil {
return
}
method := vars["method"]
behtype := vars["behtype"]
start, cmapErr := time.Parse("20060102150405", vars["start"])
if cmapErr != nil {
......@@ -372,13 +371,13 @@ func (logger *Logger) handleGetStats(w http.ResponseWriter, r *http.Request) {
logger.logErrors(r.URL.Path, cmapErr, httpErr)
return
}
data, cmapErr := logger.getStats(masID, agentID, method, behtype, start, end)
statsInfo, cmapErr := logger.getStats(masID, agentID, behtype, start, end)
if cmapErr != nil {
httpErr := httpreply.CMAPError(w, cmapErr.Error())
logger.logErrors(r.URL.Path, cmapErr, httpErr)
return
}
httpErr = httpreply.Resource(w, data, cmapErr)
httpErr = httpreply.Resource(w, statsInfo, cmapErr)
logger.logErrors(r.URL.Path, cmapErr, httpErr)
}
......@@ -539,7 +538,7 @@ func (logger *Logger) server(port int) (serv *http.Server) {
s.Path("/stats/{masid}").Methods("POST").HandlerFunc(logger.handlePostBehsStats)
s.Path("/stats/{masid}/heatmap").Methods("GET").HandlerFunc(logger.handleGetMsgHeatmap)
s.Path("/stats/{masid}/{agentid}/{method}/{behtype}/{start}/{end}").Methods("GET").HandlerFunc(logger.handleGetStats)
s.Path("/stats/{masid}/{agentid}/{behtype}/{start}/{end}").Methods("GET").HandlerFunc(logger.handleGetStats)
s.Path("/state/{masid}/{agentid}").Methods("GET").HandlerFunc(logger.handleGetState)
s.Path("/state/{masid}/{agentid}").Methods("PUT").HandlerFunc(logger.handlePutState)
......
......@@ -176,9 +176,9 @@ func (logger *Logger) getMsgHeatmap(masID int) (heatmap map[[2]int]int, err erro
return
}
// getStatistics get the data of a certain method and topic
func (logger *Logger) getStats(masID int, agentID int, method string, topic string, start time.Time, end time.Time) (data float32, err error) {
data, err = logger.stor.getStats(masID, agentID, method, topic, start, end)
// getStats get the statistical data of a certain behType
func (logger *Logger) getStats(masID int, agentID int, behType string, start time.Time, end time.Time) (statsInfo schemas.StatsInfo, err error) {
statsInfo, err = logger.stor.getStats(masID, agentID, behType, start, end)
return
}
......
......@@ -84,8 +84,8 @@ type storage interface {
// getMsgHeatmap get the msg communication frequency
getMsgHeatmap(masID int) (heatmap map[[2]int]int, err error)
// getStatistics get the data of a certain method and topic
getStats(masID int, agentID int, method string, topic string, start time.Time, end time.Time) (data float32, err error)
// getStats get the data of a certain behtype
getStats(masID int, agentID int, behType string, start time.Time, end time.Time) (statsInfo schemas.StatsInfo, err error)
// deleteAgentLogMessages deletes all log messages og an agent
deleteAgentLogMessages(masID int, agentID int) (err error)
......@@ -494,8 +494,8 @@ func getAverage(duration []int) (averageVal float32) {
return float32(sum) / float32(len(duration))
}
// getStatistics get the data of a certain method and topic
func (stor *localStorage) getStats(masID int, agentID int, method string, behType string, start time.Time, end time.Time) (data float32, err error) {
// getStats get the data of a certain behavior type
func (stor *localStorage) getStats(masID int, agentID int, behType string, start time.Time, end time.Time) (statsInfo schemas.StatsInfo, err error) {
stor.mutex.Lock()
if masID < len(stor.mas) {
if agentID < len(stor.mas[masID].agents) {
......@@ -513,18 +513,12 @@ func (stor *localStorage) getStats(masID int, agentID int, method string, behTyp
if endIndex-startIndex >= 0 {
logs := make([]schemas.BehStats, endIndex-startIndex)
copy(logs, stor.mas[masID].agents[agentID].behsStats[behType][startIndex:endIndex])
switch method {
case "max":
data = float32(getMax(getDuration(logs)))
case "min":
data = float32(getMin(getDuration(logs)))
case "count":
data = float32(len(logs))
case "average":
data = getAverage((getDuration(logs)))
default:
err = errors.New("wrong method")
}
statsInfo.List = logs
statsInfo.Max = getMax(getDuration(logs))
statsInfo.Min = getMin(getDuration(logs))
statsInfo.Count = len(logs)
statsInfo.Average = getAverage((getDuration(logs)))
}
}
}
......
......@@ -259,85 +259,85 @@ type ACLMessage struct {
// String outputs message
func (msg ACLMessage) String() (ret string) {
ret = "Sender: " + strconv.Itoa(msg.Sender) + "; Receiver: " + strconv.Itoa(msg.Receiver) +
"; Timestamp: " + msg.Timestamp.String() + "; "
ret = "Sender: " + strconv.Itoa(msg.Sender) + ";Receiver: " + strconv.Itoa(msg.Receiver) +
";Timestamp: " + msg.Timestamp.String() + ";"
switch msg.Protocol {
case FIPAProtNone:
ret += "Protocol: None; "
ret += "Protocol: None;"
case FIPAProtRequest:
ret += "Protocol: Request; "
ret += "Protocol: Request;"
case FIPAProtQuery:
ret += "Protocol: Query; "
ret += "Protocol: Query;"
case FIPAProtRequestWhen:
ret += "Protocol: Request When; "
ret += "Protocol: Request When;"
case FIPAProtContractNet:
ret += "Protocol: Contract-Net; "
ret += "Protocol: Contract-Net;"
case FIPAProtIteratedContractNet:
ret += "Protocol: Iterated Contract-Net; "
ret += "Protocol: Iterated Contract-Net;"
case FIPAProtEnglishAuction:
ret += "Protocol: English Auction; "
ret += "Protocol: English Auction;"
case FIPAProtDutchAuction:
ret += "Protocol: Dutch Auction; "
ret += "Protocol: Dutch Auction;"
case FIPAProtBrokering:
ret += "Protocol: Brokering; "
ret += "Protocol: Brokering;"
case FIPAProtRecruiting:
ret += "Protocol: Recruiting; "
ret += "Protocol: Recruiting;"
case FIPAProtSubscribe:
ret += "Protocol: Subscribe; "
ret += "Protocol: Subscribe;"
case FIPAProtPropose:
ret += "Protocol: Propose; "
ret += "Protocol: Propose;"
default:
ret += "Protocol: Unknown(" + strconv.Itoa(msg.Protocol) + "); "
ret += "Protocol: Unknown(" + strconv.Itoa(msg.Protocol) + ");"
}
switch msg.Performative {
case FIPAPerfNone:
ret += "Performative: None; "
ret += "Performative: None;"
case FIPAPerfAcceptProposal:
ret += "Performative: Accept Proposal; "
ret += "Performative: Accept Proposal;"
case FIPAPerfAgree:
ret += "Performative: Agree; "
ret += "Performative: Agree;"
case FIPAPerfCancel:
ret += "Performative: Cancel; "
ret += "Performative: Cancel;"
case FIPAPerfCallForProposal:
ret += "Performative: Call For Proposal; "
ret += "Performative: Call For Proposal;"
case FIPAPerfConfirm:
ret += "Performative: Confirm; "
ret += "Performative: Confirm;"
case FIPAPerfDisconfirm:
ret += "Performative: Disconfirm; "
ret += "Performative: Disconfirm;"
case FIPAPerfFailure:
ret += "Performative: Failure; "
ret += "Performative: Failure;"
case FIPAPerfInform:
ret += "Performative: Inform; "
ret += "Performative: Inform;"
case FIPAPerfInformIf:
ret += "Performative: Inform If; "
ret += "Performative: Inform If;"
case FIPAPerfInformRef:
ret += "Performative: Inform Ref; "
ret += "Performative: Inform Ref;"
case FIPAPerfNotUnderstood:
ret += "Performative: Not Understood; "
ret += "Performative: Not Understood;"
case FIPAPerfPropagate:
ret += "Performative: Propagate; "
ret += "Performative: Propagate;"
case FIPAPerfPropose:
ret += "Performative: Propose; "
ret += "Performative: Propose;"
case FIPAPerfProxy:
ret += "Performative: Proxy; "
ret += "Performative: Proxy;"
case FIPAPerfQueryIf:
ret += "Performative: Query If; "
ret += "Performative: Query If;"
case FIPAPerfQueryRef:
ret += "Performative: Query Ref; "
ret += "Performative: Query Ref;"
case FIPAPerfRefuse:
ret += "Performative: Refuse; "
ret += "Performative: Refuse;"
case FIPAPerfRejectProposal:
ret += "Performative: Reject Proposal; "
ret += "Performative: Reject Proposal;"
case FIPAPerfRequest:
ret += "Performative: Request; "
ret += "Performative: Request;"
case FIPAPerfRequestWhen:
ret += "Performative: Request When; "
ret += "Performative: Request When;"
case FIPAPerfRequestWhenever:
ret += "Performative: Request Whenever; "
ret += "Performative: Request Whenever;"
case FIPAPerfSubscribe:
ret += "Performative: Subscribe; "
ret += "Performative: Subscribe;"
default:
ret += "Performative: Unknown(" + strconv.Itoa(msg.Performative) + "); "
ret += "Performative: Unknown(" + strconv.Itoa(msg.Performative) + ");"
}
ret += "Content: " + msg.Content
return
......
......@@ -44,6 +44,8 @@ THE SOFTWARE.
package schemas
import "time"
// MQTTConfig contains the host and port configuration of the broker and indicates if it is active
type MQTTConfig struct {
Active bool `json:"active"` // indicates if MQTT is active/usable
......@@ -57,6 +59,25 @@ type MQTTMessage struct {
Content []byte // Denotes the content of the message
}
// behStats contains behavior information
type BehStats struct {
MASID int `json:"masid"` // ID of MAS agent runs in
AgentID int `json:"agentid"` // ID of agent
BehType string `json:"behtype"`
Start time.Time `json:"start"`
End time.Time `json:"end"`
Duration int `json:"duration"`
}
// StatsInfo struct representing the statistics info
type StatsInfo struct {
Max int `json:"max"`
Min int `json:"min"`
Count int `json:"count"`
Average float32 `json:"average"`
List []BehStats `json:"list"`
}
// String outputs message
func (msg MQTTMessage) String() (ret string) {
ret = "Topic: " + msg.Topic + ";Content: " + string(msg.Content)
......
......@@ -79,16 +79,6 @@ type LogSeries struct {
Value float64 `json:"value"` // value of the series item
}
// logBeh contains behavior information
type BehStats struct {
MASID int `json:"masid"` // ID of MAS agent runs in
AgentID int `json:"agentid"` // ID of agent
BehType string `json:"behtype"`
Start time.Time `json:"start"`
End time.Time `json:"end"`
Duration int `json:"duration"`
}
// State contains the state of an agent as byte array (json)
type State struct {
MASID int `json:"masid"` // ID of MAS agent runs in
......
export interface StatsInfo {
max: number;
min: number;
count: number;
average: number;
list: BehStats[]
}
interface BehStats {
start: string;
end: string;
duration: number;
}
\ No newline at end of file
......@@ -363,22 +363,30 @@ ngx-charts-bubble-chart {
display: none;
}
/* css for statistics */
.statis-dashboard {
.stats-dashboard {
margin-top: 50px;
}
.statis-item {
.card {
margin-bottom: 50px;
}
.statis-item > .card {
.stats-dashboard > .card {
background-color: var(--light-primary-color);
border: none;
border-radius: 30px;
height: 200px;
height: 150px;
}
.pagination /deep/ .ngx-pagination .current {
background:var(--light-primary-color);
}
.card-title {
color: white;
text-align: center;
......@@ -386,10 +394,10 @@ ngx-charts-bubble-chart {
.card-text {
color: white;
font-size: 50px;
font-size: 30px;
text-align: center;
vertical-align: middle;
line-height: 90px;
line-height: 60px;
}
......
......@@ -242,16 +242,53 @@
<div *ngIf="currState==='statistics'">
<div class="row" >
<div class="container">
<div class="row statis-dashboard">
<div *ngFor="let item of statisInfo" class="col-sm-6 statis-item">
<div class="card">
<div class="row justify-content-around" >
<div *ngIf="statsInfo !== null" class="col-sm-5">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Start</th>
<th scope="col">End</th>
<th scope="col">Duration</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of statsInfo.list | paginate: { itemsPerPage: 10, currentPage: q }; index as i ">
<th scope="row">{{ (q - 1) * 10 + i + 1 }}</th>
<td>{{item.start.split(".")[0]}}</td>
<td>{{item.end.split(".")[0]}}</td>
<td>{{convertSecond(item.duration)}}</td>
</tr>
</tbody>
</table>
<pagination-controls class="pagination justify-content-end" (pageChange)="q = $event"></pagination-controls>
</div>
<div *ngIf="statsInfo !== null" class="col-sm-4">
<div class="row justify-content-around stats-dashboard">
<div class="card col-sm-5">
<div class="card-body">
<h1 class="card-title">{{ item.method }}</h1>
<p class="card-text">{{item.value}}</p>
<h1 class="card-title">max</h1>
<p class="card-text">{{convertSecond(statsInfo.max)}}</p>
</div>
</div>
<div class="card col-sm-5">