diff --git a/development/mysql-backend/1_Constraint.sql b/development/mysql-backend/1_Constraint.sql index 549a2094f..b47b3fedf 100644 --- a/development/mysql-backend/1_Constraint.sql +++ b/development/mysql-backend/1_Constraint.sql @@ -1,3 +1,2 @@ ALTER TABLE isu ADD CONSTRAINT `isu_user_id` FOREIGN KEY (jia_user_id) REFERENCES user(jia_user_id); ALTER TABLE isu_condition ADD CONSTRAINT `isu_condition_isu_uuid` FOREIGN KEY (jia_isu_uuid) REFERENCES isu(jia_isu_uuid); -ALTER TABLE graph ADD CONSTRAINT `graph_isu_uuid` FOREIGN KEY (jia_isu_uuid) REFERENCES isu(jia_isu_uuid); diff --git a/development/mysql-backend/2_Init.sql b/development/mysql-backend/2_Init.sql index 1f5c83a18..2c21d6237 100644 --- a/development/mysql-backend/2_Init.sql +++ b/development/mysql-backend/2_Init.sql @@ -15,16 +15,6 @@ /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; --- --- Dumping data for table `graph` --- - -LOCK TABLES `graph` WRITE; -/*!40000 ALTER TABLE `graph` DISABLE KEYS */; -INSERT INTO `graph` VALUES ('0694e4d7-dfce-4aec-b7ca-887ac42cfb8f','2021-06-16 00:00:00','{\"score\": 70, \"detail\": {\"dirty\": 0, \"over_weight\": 0}, \"sitting\": 0}','2021-06-16 02:33:40.596857','2021-06-16 02:33:40.596857'),('0694e4d7-dfce-4aec-b7ca-887ac42cfb8f','2021-06-16 01:00:00','{\"score\": 50, \"detail\": {\"dirty\": -10, \"over_weight\": -10}, \"sitting\": 100}','2021-06-16 02:33:40.598760','2021-06-16 02:33:40.598760'),('f012233f-c50e-4349-9473-95681becff1e','2021-06-16 00:00:00','{\"score\": 50, \"detail\": {\"dirty\": -10, \"over_weight\": -10}, \"sitting\": 100}','2021-06-16 02:33:40.600613','2021-06-16 02:33:40.600613'),('f012233f-c50e-4349-9473-95681becff1e','2021-06-16 01:00:00','{\"score\": 30, \"detail\": {\"dirty\": -10, \"over_weight\": -10}, \"sitting\": 100}','2021-06-16 02:33:40.602674','2021-06-16 02:33:40.602674'); -/*!40000 ALTER TABLE `graph` ENABLE KEYS */; -UNLOCK TABLES; - -- -- Dumping data for table `isu` -- diff --git a/extra/initial-data/graph/graph.go b/extra/initial-data/graph/graph.go deleted file mode 100644 index bbf9f5291..000000000 --- a/extra/initial-data/graph/graph.go +++ /dev/null @@ -1,162 +0,0 @@ -package graph - -// copy from github.com/isucon/isucon11-qualify/webapp/go/main.go - -import ( - "encoding/json" - "fmt" - "strings" - "time" - - "github.com/jmoiron/sqlx" -) - -type IsuLog struct { - JIAIsuUUID string `db:"jia_isu_uuid" json:"jia_isu_uuid"` - Timestamp time.Time `db:"timestamp" json:"timestamp"` - IsSitting bool `db:"is_sitting" json:"is_sitting"` - Condition string `db:"condition" json:"condition"` - Message string `db:"message" json:"message"` - CreatedAt time.Time `db:"created_at" json:"created_at"` -} - -type GraphData struct { - Score int `json:"score"` - Sitting int `json:"sitting"` - Detail map[string]int `json:"detail"` -} - -var scorePerCondition = map[string]int{ - "is_dirty": -1, - "is_overweight": -1, - "is_broken": -5, -} - -func UpdateGraph(x sqlx.Ext, jiaIsuUUID string, updatedAt time.Time) error { - // IsuLogを一時間ごとの区切りに分け、区切りごとにスコアを計算する - isuLogCluster := []IsuLog{} // 一時間ごとの纏まり - var tmpIsuLog IsuLog - valuesForUpdate := []interface{}{} //5個1組、更新するgraphの各行のデータ - rows, err := x.Queryx("SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = ? ORDER BY `timestamp` ASC", jiaIsuUUID) - if err != nil { - return err - } - //一時間ごとに区切る - var startTime time.Time - for rows.Next() { - err = rows.StructScan(&tmpIsuLog) - if err != nil { - return err - } - tmpTime := truncateAfterHours(tmpIsuLog.Timestamp) - if startTime != tmpTime { - if len(isuLogCluster) > 0 { - //tmpTimeは次の一時間なので、それ以外を使ってスコア計算 - data, err := calculateGraphData(isuLogCluster) - if err != nil { - return fmt.Errorf("failed to calculate graph: %v", err) - } - valuesForUpdate = append(valuesForUpdate, jiaIsuUUID, startTime, data, updatedAt, updatedAt) - } - - //次の一時間の探索 - startTime = tmpTime - isuLogCluster = []IsuLog{} - } - isuLogCluster = append(isuLogCluster, tmpIsuLog) - } - if len(isuLogCluster) > 0 { - //最後の一時間分 - data, err := calculateGraphData(isuLogCluster) - if err != nil { - return fmt.Errorf("failed to calculate graph: %v", err) - } - valuesForUpdate = append(valuesForUpdate, jiaIsuUUID, startTime, data, updatedAt, updatedAt) - } - - //insert or update - params := strings.Repeat("(?,?,?,?,?),", len(valuesForUpdate)/5) - params = params[:len(params)-1] - _, err = x.Exec("INSERT INTO `graph` (`jia_isu_uuid`, `start_at`, `data`, `created_at`, `updated_at`) VALUES "+ - params+ - " ON DUPLICATE KEY UPDATE `data` = VALUES(`data`), `updated_at` = VALUES(`updated_at`)", - valuesForUpdate..., - ) - if err != nil { - return err - } - - return nil -} - -//分以下を切り捨て、一時間単位にする関数 -func truncateAfterHours(t time.Time) time.Time { - return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, t.Location()) -} - -//スコア計算をする関数 -func calculateGraphData(isuLogCluster []IsuLog) ([]byte, error) { - graph := &GraphData{} - - //sitting - sittingCount := 0 - for _, log := range isuLogCluster { - if log.IsSitting { - sittingCount++ - } - } - graph.Sitting = sittingCount * 100 / len(isuLogCluster) - - //score&detail - graph.Score = 100 - //condition要因の減点 - graph.Detail = map[string]int{} - for key := range scorePerCondition { - graph.Detail[key] = 0 - } - for _, log := range isuLogCluster { - conditions := map[string]bool{} - //DB上にある is_dirty=true/false,is_overweight=true/false,... 形式のデータを - //map[string]bool形式に変換 - for _, cond := range strings.Split(log.Condition, ",") { - keyValue := strings.Split(cond, "=") - if len(keyValue) != 2 { - continue //形式に従っていないものは無視 - } - conditions[keyValue[0]] = (keyValue[1] != "false") - } - - //trueになっているものは減点 - for key, enabled := range conditions { - if enabled { - score, ok := scorePerCondition[key] - if ok { - graph.Score += score - graph.Detail[key] += score - } - } - } - } - //スコアに影響がないDetailを削除 - for key := range scorePerCondition { - if graph.Detail[key] == 0 { - delete(graph.Detail, key) - } - } - //個数減点 - if len(isuLogCluster) < 50 { - minus := -(50 - len(isuLogCluster)) * 2 - graph.Score += minus - graph.Detail["missing_data"] = minus - } - if graph.Score < 0 { - graph.Score = 0 - } - - //JSONに変換 - graphJSON, err := json.Marshal(graph) - if err != nil { - return nil, err - } - return graphJSON, nil -} diff --git a/extra/initial-data/models/condition.go b/extra/initial-data/models/condition.go index 29eb4a65f..52ed12538 100644 --- a/extra/initial-data/models/condition.go +++ b/extra/initial-data/models/condition.go @@ -55,9 +55,5 @@ func (c Condition) Create() error { return fmt.Errorf("insert user: %w", err) } - // INSERT INTO graph - if err := graph.UpdateGraph(db, c.Isu.JIAIsuUUID, c.CreatedAt); err != nil { - log.Fatal(err) - } return nil } diff --git a/webapp/go/main.go b/webapp/go/main.go index 67c9c8051..197f8fcdf 100644 --- a/webapp/go/main.go +++ b/webapp/go/main.go @@ -121,11 +121,9 @@ type GraphData struct { //グラフ表示用 一時間のsummry type Graph struct { - JIAIsuUUID string `db:"jia_isu_uuid"` - StartAt time.Time `db:"start_at"` - Data string `db:"data"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` + JIAIsuUUID string + StartAt time.Time + Data string } type User struct { @@ -1137,11 +1135,9 @@ func getIsuGraph(c echo.Context) error { return c.String(http.StatusNotFound, "isu not found") } - var graphList []Graph - err = tx.Select(&graphList, "SELECT * FROM `graph` WHERE `jia_isu_uuid` = ? AND ? <= `start_at` AND `start_at` <= ? ORDER BY `start_at` ASC ", - jiaIsuUUID, date, date.Add(time.Hour*24)) + graphList, err := getGraphDataList(tx, jiaIsuUUID, date) if err != nil { - c.Logger().Errorf("db error: %v", err) + c.Logger().Errorf("cannot get graph: %v", err) return c.NoContent(http.StatusInternalServerError) } @@ -1542,13 +1538,6 @@ func postIsuCondition(c echo.Context) error { } - // getGraph用のデータを計算し、DBを更新する - err = updateGraph(tx, jiaIsuUUID) - if err != nil { - c.Logger().Errorf("failed to update graph: %v", err) - return c.NoContent(http.StatusInternalServerError) - } - // トランザクション終了 err = tx.Commit() if err != nil { @@ -1559,22 +1548,21 @@ func postIsuCondition(c echo.Context) error { return c.NoContent(http.StatusCreated) } -// getGraph用のデータを計算し、DBを更新する -func updateGraph(tx *sqlx.Tx, jiaIsuUUID string) error { +func getGraphDataList(tx *sqlx.Tx, jiaIsuUUID string, date time.Time) ([]Graph, error) { // IsuConditionを一時間ごとの区切りに分け、区切りごとにスコアを計算する IsuConditionCluster := []IsuCondition{} // 一時間ごとの纏まり var tmpIsuCondition IsuCondition - valuesForUpdate := []interface{}{} //3個1組、更新するgraphの各行のデータ rows, err := tx.Queryx("SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = ? ORDER BY `timestamp` ASC", jiaIsuUUID) if err != nil { - return err + return nil, err } + graphDatas := []Graph{} //一時間ごとに区切る var startTime time.Time for rows.Next() { err = rows.StructScan(&tmpIsuCondition) if err != nil { - return err + return nil, err } tmpTime := truncateAfterHours(tmpIsuCondition.Timestamp) if startTime != tmpTime { @@ -1582,9 +1570,9 @@ func updateGraph(tx *sqlx.Tx, jiaIsuUUID string) error { //tmpTimeは次の一時間なので、それ以外を使ってスコア計算 data, err := calculateGraphData(IsuConditionCluster) if err != nil { - return fmt.Errorf("failed to calculate graph: %v", err) + return nil, fmt.Errorf("failed to calculate graph: %v", err) } - valuesForUpdate = append(valuesForUpdate, jiaIsuUUID, startTime, data) + graphDatas = append(graphDatas, Graph{JIAIsuUUID: jiaIsuUUID, StartAt: startTime, Data: string(data)}) } //次の一時間の探索 @@ -1597,24 +1585,28 @@ func updateGraph(tx *sqlx.Tx, jiaIsuUUID string) error { //最後の一時間分 data, err := calculateGraphData(IsuConditionCluster) if err != nil { - return fmt.Errorf("failed to calculate graph: %v", err) + return nil, fmt.Errorf("failed to calculate graph: %v", err) } - valuesForUpdate = append(valuesForUpdate, jiaIsuUUID, startTime, data) + graphDatas = append(graphDatas, Graph{JIAIsuUUID: jiaIsuUUID, StartAt: startTime, Data: string(data)}) } - //insert or update - params := strings.Repeat("(?,?,?),", len(valuesForUpdate)/3) - params = params[:len(params)-1] - _, err = tx.Exec("INSERT INTO `graph` (`jia_isu_uuid`, `start_at`, `data`) VALUES "+ - params+ - " ON DUPLICATE KEY UPDATE `data` = VALUES(`data`)", - valuesForUpdate..., - ) - if err != nil { - return err + // 24時間分のグラフデータだけを取り出す処理 + endDate := date.Add(time.Hour * 24) + startIndex := 0 + endNextIndex := len(graphDatas) + for i, graph := range graphDatas { + if startIndex == 0 && !graph.StartAt.Before(date) { + startIndex = i + } + if endNextIndex == len(graphDatas) && graph.StartAt.After(endDate) { + endNextIndex = i + } + } + if endNextIndex < startIndex { + return []Graph{}, nil } - return nil + return graphDatas[startIndex : endNextIndex], nil } //分以下を切り捨て、一時間単位にする関数