diff --git a/pkg/chessai/player/ai/performance_logger.go b/pkg/chessai/player/ai/performance_logger.go index 96388c8..04c8c47 100644 --- a/pkg/chessai/player/ai/performance_logger.go +++ b/pkg/chessai/player/ai/performance_logger.go @@ -7,7 +7,6 @@ import ( "github.com/Vadman97/ChessAI3/pkg/chessai/color" "log" "os" - "strconv" ) type PerformanceLogger struct { @@ -18,6 +17,10 @@ type PerformanceLogger struct { ExcelFile *excelize.File } +var startingColPruning byte = 'A' +var startingColMoveCache byte = 'K' +var startingColAttackableCache byte = 'T' + /** * Creates a new PerformanceLogger. */ @@ -45,13 +48,34 @@ func (logger *PerformanceLogger) setupExcelFile() { } } +/** + * Sets up all excel table row headings. + */ func (logger *PerformanceLogger) setupExcelRowHeadings(sheet string) { - logger.ExcelFile.SetCellValue(sheet, "A1", "Turn") - logger.ExcelFile.SetCellValue(sheet, "B1", "Considered") - logger.ExcelFile.SetCellValue(sheet, "C1", "Pruned") - logger.ExcelFile.SetCellValue(sheet, "D1", "Pruned AB") - logger.ExcelFile.SetCellValue(sheet, "E1", "Pruned Trans") - logger.ExcelFile.SetCellValue(sheet, "F1", "AB Improved Trans") + logger.setupExcelRowHeadingsForTable(sheet, + "Pruning Statistics", + []string{"Turn", "Considered", "Pruned", "Pruned AB", "Pruned Trans", "AB Improved Trans"}, + startingColPruning) + cacheHeadings := []string{"Turn", "Entries", "Reads", "Locks used", "Writes", "Hit Ratio", "Read Ratio"} + logger.setupExcelRowHeadingsForTable(sheet, "Move Cache Statistics", cacheHeadings, startingColMoveCache) + logger.setupExcelRowHeadingsForTable(sheet, "Attackable Cache Statistics", cacheHeadings, + startingColAttackableCache) +} + +/** + * Creates row headings for a logging table. + */ +func (logger *PerformanceLogger) setupExcelRowHeadingsForTable(sheet string, tableHeading string, + columnHeadings []string, startColumn byte) { + excel := logger.ExcelFile + for index, heading := range columnHeadings { + cell := fmt.Sprintf("%c%d", startColumn+byte(index), 2) + excel.SetCellValue(sheet, cell, heading) + } + firstCell := fmt.Sprintf("%c%d", startColumn, 1) + lastCell := fmt.Sprintf("%c%d", startColumn+byte(len(columnHeadings))-1, 1) + excel.MergeCell(sheet, firstCell, lastCell) + excel.SetCellValue(sheet, firstCell, tableHeading) } /** @@ -67,37 +91,91 @@ func (logger *PerformanceLogger) MarkPerformance(b *board.Board, m *ScoredMove, } /** - * Call this function after the game is complete and no more logging is desired. + * Call this function after the game is complete and no more logging is desired. It will generate all charts and save + * the excel file. */ func (logger *PerformanceLogger) CompletePerformanceLog(white *Player, black *Player) { - logger.generatePruningBreakdownChart(white) - logger.generatePruningBreakdownChart(black) + logger.generateChartsForPlayer(white) + logger.generateChartsForPlayer(black) err := logger.ExcelFile.SaveAs(logger.ExcelFileName) if err != nil { log.Fatal("Cannot save excel performance log.", err) } } +/** + * Generates all charts for one player. + */ +func (logger *PerformanceLogger) generateChartsForPlayer(p *Player) { + logger.generatePruningBreakdownChart(p) + logger.generateCacheCharts(p, "Move", startingColMoveCache) + logger.generateCacheCharts(p, "Attackable", startingColAttackableCache) +} + +/** + * Generates pruning breakdown - AB vs Transposition. + */ func (logger *PerformanceLogger) generatePruningBreakdownChart(p *Player) { - row := strconv.Itoa(p.TurnCount + 4) - var chartDataString string - c := color.Names[p.PlayerColor] - series := `{"name":"%s!$%c$1", "categories":"%s!$%c$2:$%c$%d","values":"%s!$%c$2:$%c$%d"}` - chartDataString += `{"type":"barPercentStacked","series":[` - chartDataString += fmt.Sprintf(series, c, 'D', c, 'A', 'A', p.TurnCount+1, c, 'D', 'D', p.TurnCount+1) - chartDataString += "," - chartDataString += fmt.Sprintf(series, c, 'E', c, 'A', 'A', p.TurnCount+1, c, 'E', 'E', p.TurnCount+1) - chartDataString += `],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,` - chartDataString += `"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":true},` - chartDataString += `"title":{"name":"Pruning Breakdown"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,` - chartDataString += `"show_leader_lines":false,"show_percent":true,"show_series_name":false,"show_val":false},` - chartDataString += `"show_blanks_as":"zero"}` - err := logger.ExcelFile.AddChart(color.Names[p.PlayerColor], "B"+row, chartDataString) + logger.generateChart("barPercentStacked", "Pruning Breakdown", p, startingColPruning, + p.TurnCount+4, []byte{startingColPruning + byte(3), startingColPruning + byte(4)}) +} + +/** + * Generates two charts for a cache. + * Chart 1 - "Utilization" - Hit and Read ratios + * Chart 2 - "Size" - Entries, Reads, Writes, and Lock Usage + */ +func (logger *PerformanceLogger) generateCacheCharts(p *Player, cacheName string, startingCol byte) { + logger.generateChart("scatter", cacheName+" Cache Utilization", p, startingCol, p.TurnCount+4, + []byte{startingColMoveCache + byte(5), startingColMoveCache + byte(6)}, + ) + logger.generateChart("scatter", cacheName+" Cache Size", p, startingCol, p.TurnCount+24, + []byte{ + startingColMoveCache + byte(1), + startingColMoveCache + byte(2), + startingColMoveCache + byte(3), + startingColMoveCache + byte(4), + }) +} + +/** + * Generates a chart. + */ +func (logger *PerformanceLogger) generateChart(chartType string, chartTitle string, p *Player, tableStartCol byte, + chartRow int, seriesCols []byte) { + chartCell := fmt.Sprintf("%c%d", tableStartCol, chartRow) + sheet := color.Names[p.PlayerColor] + lastTurnRow := p.TurnCount + 2 + var chartData string + chartData += fmt.Sprintf(`{"type":"%s","series":[`, chartType) + for index, element := range seriesCols { + chartData += logger.generateSeriesString(sheet, lastTurnRow, element, tableStartCol) + if index+1 != len(seriesCols) { + chartData += `,` + } + } + chartData += `],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,` + chartData += `"lock_aspect_ratio":false,"locked":false},"legend":{"position":"top","show_legend_key":true},` + chartData += fmt.Sprintf(`"title":{"name":"%s"},`, chartTitle) + chartData += `"plotarea":{"show_bubble_size":true,"show_cat_name":false,` + chartData += `"show_leader_lines":false,"show_percent":false,"show_series_name":false,"show_val":false},` + chartData += `"show_blanks_as":"zero"}` + err := logger.ExcelFile.AddChart(color.Names[p.PlayerColor], chartCell, chartData) if err != nil { - fmt.Println(err) + log.Fatal(err) } } +/** + * Generates a series string for a chart. + */ +func (logger *PerformanceLogger) generateSeriesString(sheet string, lastTurnRow int, seriesCol byte, + categoriesCol byte) string { + series := `{"name":"%s!$%c$2", "categories":"%s!$%c$3:$%c$%d","values":"%s!$%c$3:$%c$%d"}` + return fmt.Sprintf(series, sheet, seriesCol, sheet, categoriesCol, categoriesCol, lastTurnRow, sheet, seriesCol, + seriesCol, lastTurnRow) +} + /** * Performs simple logging to log file. */ @@ -118,28 +196,55 @@ func (logger *PerformanceLogger) markPerformanceToLog(b *board.Board, m *ScoredM result += b.MoveCache.PrintMetrics() result += fmt.Sprintf("Attack Move cache metrics\n") result += b.AttackableCache.PrintMetrics() - result += "A" + string(p.TurnCount+1) _, _ = fmt.Fprint(file, result) } /** - * Performs logging to .xlsx file for a graphical representation. + * Performs logging to .xlsx file to build various tables. */ func (logger *PerformanceLogger) markPerformanceToExcel(b *board.Board, m *ScoredMove, p *Player) { - logger.markMetricsPerformanceToExcel(p) + logger.markMetricsToExcelTable(p, + []interface{}{ + p.TurnCount, + p.Metrics.MovesConsidered, + p.Metrics.MovesPrunedAB + p.Metrics.MovesPrunedTransposition, + p.Metrics.MovesPrunedAB, + p.Metrics.MovesPrunedTransposition, + p.Metrics.MovesABImprovedTransposition, + }, startingColPruning) + logger.markMetricsToExcelTable(p, + []interface{}{ + p.TurnCount, + b.MoveCache.GetTotalWrites(), + b.MoveCache.GetTotalReads(), + b.MoveCache.GetTotalWrites(), + b.MoveCache.GetTotalLockUsage(), + b.MoveCache.GetHitRatio(), + b.MoveCache.GetReadRatio(), + }, startingColMoveCache) + fmt.Println("Marking the attackable cache...") + logger.markMetricsToExcelTable(p, + []interface{}{ + p.TurnCount, + b.AttackableCache.GetTotalWrites(), + b.AttackableCache.GetTotalReads(), + b.AttackableCache.GetTotalWrites(), + b.AttackableCache.GetTotalLockUsage(), + b.AttackableCache.GetHitRatio(), + b.AttackableCache.GetReadRatio(), + }, startingColAttackableCache) } /** - * Writes metrics data to excel. + * Prints one turn of metrics. */ -func (logger *PerformanceLogger) markMetricsPerformanceToExcel(p *Player) { - metrics := p.Metrics - row := strconv.Itoa(p.TurnCount + 2) +func (logger *PerformanceLogger) markMetricsToExcelTable(p *Player, values []interface{}, startColumn byte) { + row := p.TurnCount + 3 sheet := color.Names[p.PlayerColor] - logger.ExcelFile.SetCellValue(sheet, "A"+row, p.TurnCount) - logger.ExcelFile.SetCellValue(sheet, "B"+row, metrics.MovesConsidered) - logger.ExcelFile.SetCellValue(sheet, "C"+row, metrics.MovesPrunedAB+metrics.MovesPrunedTransposition) - logger.ExcelFile.SetCellValue(sheet, "D"+row, metrics.MovesPrunedAB) - logger.ExcelFile.SetCellValue(sheet, "E"+row, metrics.MovesPrunedTransposition) - logger.ExcelFile.SetCellValue(sheet, "F"+row, metrics.MovesABImprovedTransposition) + excel := logger.ExcelFile + for index, value := range values { + cell := fmt.Sprintf("%c%d", startColumn+byte(index), row) + excel.SetCellValue(sheet, cell, value) + fmt.Println(fmt.Sprintf("%s should have a value of %f", cell, value)) + } } diff --git a/pkg/chessai/util/concurrent_board_map.go b/pkg/chessai/util/concurrent_board_map.go index b8ad7d3..b5201f5 100644 --- a/pkg/chessai/util/concurrent_board_map.go +++ b/pkg/chessai/util/concurrent_board_map.go @@ -63,20 +63,53 @@ func (m *ConcurrentBoardMap) Read(hash *BoardHash) (interface{}, bool) { } func (m *ConcurrentBoardMap) PrintMetrics() (result string) { - totalLockUsage := uint64(0) - totalHits := uint64(0) - totalReads := uint64(0) - totalWrites := uint64(0) + totalLockUsage := m.GetTotalLockUsage() + totalHits := m.GetTotalHits() + totalReads := m.GetTotalReads() + totalWrites := m.GetTotalWrites() + result += fmt.Sprintf("\tTotal entries in map %d. Reads %d. Writes %d\n", totalWrites, totalReads, totalWrites) + result += fmt.Sprintf("\tHit ratio %f%% (%d/%d)\n", m.GetHitRatio(), totalHits, totalReads) + result += fmt.Sprintf("\tRead ratio %f%%\n", m.GetReadRatio()) + result += fmt.Sprintf("\tLock usages in map %d\n", totalLockUsage) + return +} + +func (m *ConcurrentBoardMap) GetTotalLockUsage() uint64 { + var totalLockUsage uint64 = 0 for i := 0; i < NumSlices; i++ { totalLockUsage += m.lockUsage[i] + } + return totalLockUsage +} + +func (m *ConcurrentBoardMap) GetTotalHits() uint64 { + var totalHits uint64 = 0 + for i := 0; i < NumSlices; i++ { totalHits += m.numHits[i] + } + return totalHits +} + +func (m *ConcurrentBoardMap) GetTotalReads() uint64 { + var totalReads uint64 = 0 + for i := 0; i < NumSlices; i++ { totalReads += m.numQueries[i] + } + return totalReads +} + +func (m *ConcurrentBoardMap) GetTotalWrites() uint64 { + var totalWrites uint64 = 0 + for i := 0; i < NumSlices; i++ { totalWrites += m.numWrites[i] } - result += fmt.Sprintf("\tTotal entries in map %d. Reads %d. Writes %d\n", totalWrites, totalReads, totalWrites) - result += fmt.Sprintf("\tHit ratio %f%% (%d/%d)\n", 100.0*float64(totalHits)/float64(totalReads), - totalHits, totalReads) - result += fmt.Sprintf("\tRead ratio %f%%\n", 100.0*float64(totalReads)/float64(totalReads+totalWrites)) - result += fmt.Sprintf("\tLock usages in map %d\n", totalLockUsage) - return + return totalWrites +} + +func (m *ConcurrentBoardMap) GetHitRatio() float64 { + return 100.0 * float64(m.GetTotalHits()) / float64(m.GetTotalReads()) +} + +func (m *ConcurrentBoardMap) GetReadRatio() float64 { + return 100.0 * float64(m.GetTotalReads()) / float64(m.GetTotalReads()+m.GetTotalWrites()) }