diff --git a/core/src/main/kotlin/plus/rua/project/CalendarViewModel.kt b/core/src/main/kotlin/plus/rua/project/CalendarViewModel.kt index 05fb802..f6175a6 100644 --- a/core/src/main/kotlin/plus/rua/project/CalendarViewModel.kt +++ b/core/src/main/kotlin/plus/rua/project/CalendarViewModel.kt @@ -23,6 +23,28 @@ import plus.rua.project.ui.COLLAPSE_THRESHOLD import plus.rua.project.ui.FLING_VELOCITY_THRESHOLD_DP import kotlin.time.Clock +// region 性能监控工具 +private fun perfLog(tag: String, msg: String) { + println("[PERF:${Thread.currentThread().name}] $tag | $msg") +} + +private inline fun perfMeasure(tag: String, block: () -> T): T { + val start = System.nanoTime() + val result = block() + val elapsedMs = (System.nanoTime() - start) / 1_000_000.0 + perfLog(tag, "elapsed=${"%.3f".format(elapsedMs)}ms") + return result +} + +private inline fun perfMeasureResult(tag: String, block: () -> T): T { + val start = System.nanoTime() + val result = block() + val elapsedMs = (System.nanoTime() - start) / 1_000_000.0 + perfLog(tag, "result=$result elapsed=${"%.3f".format(elapsedMs)}ms") + return result +} +// endregion + /** * 日历日期数据,用于网格单元格渲染。 * @@ -113,36 +135,53 @@ class CalendarViewModel( * 当前视图被直接移除;动画只作用在目标视图的 scale/alpha 上。 */ fun toggleYearView() { + val direction = if (isYearView) "Year→Month" else "Month→Year" + perfLog("VM.toggleYearView", "START direction=$direction isCollapsed=$isCollapsed yearViewYear=$yearViewYear selectedDate=$selectedDate") + val toggleStart = System.nanoTime() yearViewJob?.cancel() yearViewJob = coroutineScope.launch { if (isYearView) { - // 年 → 月:先启动动画(年视图开始淡出),等一帧后翻转 isYearView(月视图开始组合) composeTraceBeginSection("YearView→MonthView") + val snapStart = System.nanoTime() _yearViewAnimatable.snapTo(1f) + perfLog("VM.toggleYearView", "snapTo(1f) elapsed=${(System.nanoTime() - snapStart) / 1_000_000.0}ms") val animJob = launch { + val animStart = System.nanoTime() _yearViewAnimatable.animateTo( 0f, tween(400, easing = FastOutSlowInEasing) ) + perfLog("VM.toggleYearView", "animateTo(0f) elapsed=${(System.nanoTime() - animStart) / 1_000_000.0}ms") } + val frameStart = System.nanoTime() withFrameNanos { } + perfLog("VM.toggleYearView", "withFrameNanos elapsed=${(System.nanoTime() - frameStart) / 1_000_000.0}ms") + val flipStart = System.nanoTime() isYearView = false - animJob.join() + perfLog("VM.toggleYearView", "isYearView=false flip elapsed=${(System.nanoTime() - flipStart) / 1_000_000.0}ms") composeTraceEndSection() } else { - // 月 → 年:直接切换,折叠态下周视图的 sharedElement 缩小到 MiniMonth 更自然 composeTraceBeginSection("MonthView→YearView") + val snapStart = System.nanoTime() yearViewYear = selectedDate.year _yearViewAnimatable.snapTo(0f) + perfLog("VM.toggleYearView", "snapTo(0f) + set yearViewYear=$yearViewYear elapsed=${(System.nanoTime() - snapStart) / 1_000_000.0}ms") val animJob = launch { + val animStart = System.nanoTime() _yearViewAnimatable.animateTo( 1f, tween(400, easing = FastOutSlowInEasing) ) + perfLog("VM.toggleYearView", "animateTo(1f) elapsed=${(System.nanoTime() - animStart) / 1_000_000.0}ms") } + val frameStart = System.nanoTime() withFrameNanos { } + perfLog("VM.toggleYearView", "withFrameNanos elapsed=${(System.nanoTime() - frameStart) / 1_000_000.0}ms") + val flipStart = System.nanoTime() isYearView = true - animJob.join() + perfLog("VM.toggleYearView", "isYearView=true flip elapsed=${(System.nanoTime() - flipStart) / 1_000_000.0}ms") composeTraceEndSection() } + val totalMs = (System.nanoTime() - toggleStart) / 1_000_000.0 + perfLog("VM.toggleYearView", "END direction=$direction total=${"%.3f".format(totalMs)}ms") } } @@ -151,18 +190,27 @@ class CalendarViewModel( */ @Suppress("DEPRECATION") // monthNumber 无替代 API fun selectMonthFromYearView(month: Int) { + val totalStart = System.nanoTime() + perfLog("VM.selectMonthFromYearView", "START month=$month yearViewYear=$yearViewYear") composeTraceBeginSection("YearView:SelectMonth") val date = if (yearViewYear == today.year && today.month.number == month) today else LocalDate(yearViewYear, month, 1) + val dateSetStart = System.nanoTime() selectedDate = date + perfLog("VM.selectMonthFromYearView", "selectedDate=$selectedDate elapsed=${(System.nanoTime() - dateSetStart) / 1_000_000.0}ms") + val flipStart = System.nanoTime() isYearView = false + perfLog("VM.selectMonthFromYearView", "isYearView=false elapsed=${(System.nanoTime() - flipStart) / 1_000_000.0}ms") yearViewJob?.cancel() yearViewJob = coroutineScope.launch { - withFrameNanos { } + val animStart = System.nanoTime() _yearViewAnimatable.animateTo( 0f, tween(400, easing = FastOutSlowInEasing) ) + perfLog("VM.selectMonthFromYearView", "animateTo(0f) elapsed=${(System.nanoTime() - animStart) / 1_000_000.0}ms") composeTraceEndSection() + val totalMs = (System.nanoTime() - totalStart) / 1_000_000.0 + perfLog("VM.selectMonthFromYearView", "END total=${"%.3f".format(totalMs)}ms") } } @@ -293,6 +341,7 @@ class CalendarViewModel( */ @Suppress("DEPRECATION") // monthNumber 无替代 API,kotlinx-datetime 尚未提供新接口 fun getMonthDays(year: Int, month: Int): List { + val start = System.nanoTime() composeTraceBeginSection("getMonthDays:$year-$month") val firstOfMonth = LocalDate(year, month, 1) val dayOfWeekOffset = firstOfMonth.dayOfWeek.ordinal @@ -313,6 +362,8 @@ class CalendarViewModel( ) } composeTraceEndSection() + val elapsedMs = (System.nanoTime() - start) / 1_000_000.0 + perfLog("VM.getMonthDays", "$year-$month rows=$rows totalDays=$totalDays elapsed=${"%.3f".format(elapsedMs)}ms") return result } } \ No newline at end of file diff --git a/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthPage.kt b/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthPage.kt index 2139735..9add5e7 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthPage.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthPage.kt @@ -8,7 +8,10 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.graphicsLayer @@ -23,6 +26,12 @@ import kotlinx.datetime.number import kotlinx.datetime.plus import plus.rua.project.ShiftKind +// region 性能监控工具 +private fun monthPagePerfLog(tag: String, msg: String) { + println("[MONTH-PAGE-PERF:${Thread.currentThread().name}] $tag | $msg") +} +// endregion + /** * 月度日历网格页面,支持两阶段折叠动画。 * @@ -57,8 +66,15 @@ fun CalendarMonthPage( onRowHeightMeasured: ((Int) -> Unit)? = null, modifier: Modifier = Modifier ) { + val pageStart = System.nanoTime() + var recomposeCount by remember { mutableIntStateOf(0) } + recomposeCount++ val days = remember(year, month) { - generateMonthDays(year, month) + val genStart = System.nanoTime() + val result = generateMonthDays(year, month) + val genMs = (System.nanoTime() - genStart) / 1_000_000.0 + monthPagePerfLog("CalendarMonthPage", "generateMonthDays year=$year month=$month elapsed=${"%.3f".format(genMs)}ms") + result } val density = LocalDensity.current @@ -66,6 +82,8 @@ fun CalendarMonthPage( val anchorIndex = remember(weeks, selectedDate) { weeks.indexOfFirst { week -> week.any { it.date == selectedDate } } } + val weekCount = weeks.size + monthPagePerfLog("CalendarMonthPage", "composition #$recomposeCount year=$year month=$month weeks=$weekCount collapseProgress=$collapseProgress anchorRow=$anchorIndex") val hasAnchor = anchorIndex >= 0 val h = rowHeightPx.toFloat() @@ -164,6 +182,8 @@ fun CalendarMonthPage( } } } + val totalMs = (System.nanoTime() - pageStart) / 1_000_000.0 + monthPagePerfLog("CalendarMonthPage", "END year=$year month=$month elapsed=${"%.3f".format(totalMs)}ms") } private data class DayData( diff --git a/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthView.kt b/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthView.kt index 0fb492f..e15d8ec 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthView.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/CalendarMonthView.kt @@ -77,6 +77,12 @@ import plus.rua.project.composeTraceEndSection import kotlin.math.abs import kotlin.time.Clock +// region 性能监控工具 +private fun uiPerfLog(tag: String, msg: String) { + println("[UI-PERF:${Thread.currentThread().name}] $tag | $msg") +} +// endregion + /** * 日历主界面,包含月/周视图切换、折叠动画和年视图共享元素转场。 * @@ -100,15 +106,6 @@ fun CalendarMonthView( val currentMonth by remember { derivedStateOf { viewModel.selectedDate.month.number } } val density = LocalDensity.current - // BottomCard 滑入动画:1f=完全隐藏(在下方),0f=完全显示 - val bottomCardSlideProgress by animateFloatAsState( - targetValue = if (viewModel.isYearView) 1f else 0f, - animationSpec = tween(350, delayMillis = 100, easing = FastOutSlowInEasing), - label = "bottomCardSlide" - ) - - LocalDensity.current - var rowHeightPx by remember { mutableIntStateOf(0) } var screenWidthPx by remember { mutableIntStateOf(0) } var isMenuExpanded by remember { mutableStateOf(false) } @@ -117,6 +114,10 @@ fun CalendarMonthView( isMenuExpanded = false } + // 重组计数器 + var recomposeCount by remember { mutableIntStateOf(0) } + recomposeCount++ + val pagerState = rememberPagerState(initialPage = START_PAGE, pageCount = { Int.MAX_VALUE }) // 年视图分页器 @@ -157,21 +158,23 @@ fun CalendarMonthView( } } - SharedTransitionLayout { - val sharedScope = this - Box( - modifier = modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.background) - .statusBarsPadding() - .onSizeChanged { size -> - screenWidthPx = size.width - } - ) { + uiPerfLog("CalendarMonthView", "composition #$recomposeCount isYearView=${viewModel.isYearView}") + Box( + modifier = modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .statusBarsPadding() + .onSizeChanged { size -> + screenWidthPx = size.width + } + ) { + SharedTransitionLayout { + val sharedScope = this AnimatedContent( targetState = viewModel.isYearView, label = "month_year_transition", transitionSpec = { + uiPerfLog("CalendarMonthView", "AnimatedContent transitionSpec target=$targetState initial=$initialState") val enter = fadeIn(tween(300, easing = FastOutSlowInEasing)) + slideInVertically(tween(300, easing = FastOutSlowInEasing)) { it / 6 } val exit = fadeOut(tween(200, easing = FastOutSlowInEasing)) + @@ -180,6 +183,8 @@ fun CalendarMonthView( }, modifier = Modifier.fillMaxSize() ) { isYearView -> + val contentStart = System.nanoTime() + uiPerfLog("CalendarMonthView", "AnimatedContent content lambda START isYearView=$isYearView") if (!isYearView) { composeTraceBeginSection("MonthView:Compose") val layoutReady = rowHeightPx > 0 @@ -232,16 +237,18 @@ fun CalendarMonthView( viewModel = viewModel, today = today, rowHeightPx = rowHeightPx, - modifier = Modifier - .fillMaxWidth() - .offset(y = with(density) { (bottomCardSlideProgress * 200).dp }) - .alpha(1f - bottomCardSlideProgress) + isYearView = viewModel.isYearView, + modifier = Modifier.fillMaxWidth() ) } } + val monthViewMs = (System.nanoTime() - contentStart) / 1_000_000.0 + uiPerfLog("CalendarMonthView", "MonthView content lambda END elapsed=${"%.3f".format(monthViewMs)}ms") composeTraceEndSection() } else { composeTraceBeginSection("YearView:Compose") + val yearViewStart = System.nanoTime() + uiPerfLog("CalendarMonthView", "YearView:Compose START") Column( modifier = Modifier .fillMaxSize() @@ -266,6 +273,7 @@ fun CalendarMonthView( .fillMaxWidth() .weight(1f) ) { page -> + val pageStart = System.nanoTime() val pageOffset = abs(yearPagerState.currentPageOffsetFraction) val isCurrentPage = page == yearPagerState.currentPage val crossFadeAlpha = if (isCurrentPage) { @@ -274,6 +282,7 @@ fun CalendarMonthView( pageOffset } val pageYear = viewModel.selectedDate.year + (page - START_PAGE) + uiPerfLog("CalendarMonthView", "YearPager page=$page pageYear=$pageYear crossFadeAlpha=${"%.2f".format(crossFadeAlpha)} isCurrentPage=$isCurrentPage offset=${"%.3f".format(pageOffset)}") YearGridView( year = pageYear, selectedMonth = if (pageYear == currentYear) currentMonth else 0, @@ -295,12 +304,17 @@ fun CalendarMonthView( ) } } + val yearViewMs = (System.nanoTime() - yearViewStart) / 1_000_000.0 + uiPerfLog("CalendarMonthView", "YearView:Compose END elapsed=${"%.3f".format(yearViewMs)}ms") composeTraceEndSection() } + val contentTotalMs = (System.nanoTime() - contentStart) / 1_000_000.0 + uiPerfLog("CalendarMonthView", "AnimatedContent content lambda END total=${"%.3f".format(contentTotalMs)}ms") } + } - // FAB 浮动按钮 - FloatingActionButton( + // FAB 浮动按钮 + FloatingActionButton( onClick = { isMenuExpanded = !isMenuExpanded }, modifier = Modifier .align(Alignment.BottomStart) @@ -385,7 +399,6 @@ fun CalendarMonthView( } } } -} @Composable private fun MenuIcon(color: Color, modifier: Modifier = Modifier) { @@ -415,6 +428,10 @@ private fun CalendarPagerArea( pagerState: PagerState, modifier: Modifier = Modifier ) { + val areaStart = System.nanoTime() + var areaRecomposeCount by remember { mutableIntStateOf(0) } + areaRecomposeCount++ + uiPerfLog("CalendarPagerArea", "composition #$areaRecomposeCount collapseProgress=${viewModel.collapseProgress} page=${pagerState.currentPage} rowHeightPx=$rowHeightPx") val density = LocalDensity.current val collapseProgress = viewModel.collapseProgress @@ -509,6 +526,8 @@ private fun CalendarPagerArea( modifier = pagerModifier ) } + val areaTotalMs = (System.nanoTime() - areaStart) / 1_000_000.0 + uiPerfLog("CalendarPagerArea", "END elapsed=${"%.3f".format(areaTotalMs)}ms") } @Composable @@ -516,6 +535,7 @@ private fun BottomCardArea( viewModel: CalendarViewModel, today: LocalDate, rowHeightPx: Int, + isYearView: Boolean, modifier: Modifier = Modifier ) { val density = LocalDensity.current @@ -526,12 +546,21 @@ private fun BottomCardArea( dragRangeMinPx } + val slideProgress by animateFloatAsState( + targetValue = if (isYearView) 1f else 0f, + animationSpec = tween(350, delayMillis = 100, easing = FastOutSlowInEasing), + label = "bottomCardSlide" + ) + BottomCard( viewModel = viewModel, selectedDate = viewModel.selectedDate, today = today, dragRangePx = dragRangePx, - modifier = modifier + modifier = modifier.graphicsLayer { + translationY = slideProgress * 200.dp.toPx() + alpha = 1f - slideProgress + } ) } diff --git a/core/src/main/kotlin/plus/rua/project/ui/CalendarPager.kt b/core/src/main/kotlin/plus/rua/project/ui/CalendarPager.kt index ee9ef5a..3d6e270 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/CalendarPager.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/CalendarPager.kt @@ -5,8 +5,11 @@ import androidx.compose.foundation.pager.PagerDefaults import androidx.compose.foundation.pager.PagerState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -17,6 +20,12 @@ import kotlinx.datetime.number import plus.rua.project.ShiftKind import kotlin.math.abs +// region 性能监控工具 +private fun pagerPerfLog(tag: String, msg: String) { + println("[PAGER-PERF:${Thread.currentThread().name}] $tag | $msg") +} +// endregion + /** * 月度日历分页器,HorizontalPager 实现无限左右滑动切换月份。 * @@ -51,6 +60,9 @@ fun CalendarPager( pagerState: PagerState, modifier: Modifier = Modifier ) { + var recomposeCount by remember { mutableIntStateOf(0) } + recomposeCount++ + pagerPerfLog("CalendarPager", "composition #$recomposeCount selectedDate=$selectedDate collapseProgress=$collapseProgress") val initialYear = remember { today.year } @Suppress("DEPRECATION") // monthNumber 无替代 API,kotlinx-datetime 尚未提供新接口 @@ -71,6 +83,7 @@ fun CalendarPager( flingBehavior = PagerDefaults.flingBehavior(state = pagerState), modifier = modifier ) { page -> + val pageComposeStart = System.nanoTime() val pageOffset = abs(pagerState.currentPageOffsetFraction) val isCurrentPage = page == pagerState.currentPage val alpha = if (isCurrentPage) { @@ -109,5 +122,7 @@ fun CalendarPager( onRowHeightMeasured = onRowHeightMeasured, modifier = Modifier.alpha(alpha) ) + val pageComposeMs = (System.nanoTime() - pageComposeStart) / 1_000_000.0 + pagerPerfLog("CalendarPager", "page=$page year=$year month=$month alpha=${"%.2f".format(alpha)} elapsed=${"%.3f".format(pageComposeMs)}ms") } } \ No newline at end of file diff --git a/core/src/main/kotlin/plus/rua/project/ui/WeekPager.kt b/core/src/main/kotlin/plus/rua/project/ui/WeekPager.kt index d351cea..f396d6c 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/WeekPager.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/WeekPager.kt @@ -8,7 +8,10 @@ import androidx.compose.foundation.pager.PagerDefaults import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -21,6 +24,12 @@ import kotlinx.datetime.plus import plus.rua.project.ShiftKind import kotlin.math.abs +// region 性能监控工具 +private fun weekPerfLog(tag: String, msg: String) { + println("[WEEK-PERF:${Thread.currentThread().name}] $tag | $msg") +} +// endregion + /** * 周视图分页器,折叠状态下显示选中日期所在周,支持左右滑动切换周。 * @@ -42,6 +51,9 @@ fun WeekPager( showLegalHoliday: Boolean, modifier: Modifier = Modifier ) { + var recomposeCount by remember { mutableIntStateOf(0) } + recomposeCount++ + weekPerfLog("WeekPager", "composition #$recomposeCount selectedDate=$selectedDate") val initialWeekMonday = remember { selectedDate.toWeekMonday() } val pagerState = rememberPagerState( initialPage = START_PAGE, @@ -70,6 +82,7 @@ fun WeekPager( flingBehavior = PagerDefaults.flingBehavior(state = pagerState), modifier = modifier ) { page -> + val pageStart = System.nanoTime() val pageOffset = abs(pagerState.currentPageOffsetFraction) val isCurrentPage = page == pagerState.currentPage val alpha = if (isCurrentPage) { @@ -99,5 +112,7 @@ fun WeekPager( ) } } + val pageMs = (System.nanoTime() - pageStart) / 1_000_000.0 + weekPerfLog("WeekPager", "page=$page weekMonday=$weekMonday elapsed=${"%.3f".format(pageMs)}ms") } } \ No newline at end of file diff --git a/core/src/main/kotlin/plus/rua/project/ui/YearGridView.kt b/core/src/main/kotlin/plus/rua/project/ui/YearGridView.kt index 4023aca..d7f4ed2 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/YearGridView.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/YearGridView.kt @@ -25,7 +25,9 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -50,6 +52,12 @@ import plus.rua.project.composeTraceEndSection private val WEEKDAY_LABELS = listOf("一", "二", "三", "四", "五", "六", "日") +// region 性能监控工具 +private fun yearPerfLog(tag: String, msg: String) { + println("[YEAR-PERF:${Thread.currentThread().name}] $tag | $msg") +} +// endregion + private data class MiniMonthColors( val titleSelected: Color, val titleNormal: Color, @@ -82,6 +90,10 @@ fun YearGridView( animatedVisibilityScope: AnimatedVisibilityScope, modifier: Modifier = Modifier ) { + val yearGridStart = System.nanoTime() + var yearGridRecomposeCount by remember { mutableIntStateOf(0) } + yearGridRecomposeCount++ + yearPerfLog("YearGridView", "composition #$yearGridRecomposeCount year=$year selectedMonth=$selectedMonth") composeTraceBeginSection("YearGridView:$year") // P0-F: 主题色在 YearGridView 级别一次性读取并缓存 @@ -196,6 +208,8 @@ fun YearGridView( } } composeTraceEndSection() + val yearGridTotalMs = (System.nanoTime() - yearGridStart) / 1_000_000.0 + yearPerfLog("YearGridView", "END year=$year elapsed=${"%.3f".format(yearGridTotalMs)}ms") } /** @@ -308,6 +322,7 @@ private data class MiniDayData( @Suppress("DEPRECATION") // monthNumber 无替代 API private fun generateMiniMonthDays(year: Int, month: Int): List { + val start = System.nanoTime() composeTraceBeginSection("generateMiniMonthDays:$year-$month") val firstOfMonth = LocalDate(year, month, 1) val offset = firstOfMonth.dayOfWeek.ordinal @@ -325,6 +340,8 @@ private fun generateMiniMonthDays(year: Int, month: Int): List { ) } composeTraceEndSection() + val elapsedMs = (System.nanoTime() - start) / 1_000_000.0 + yearPerfLog("generateMiniMonthDays", "$year-$month rows=$rows totalDays=$totalDays elapsed=${"%.3f".format(elapsedMs)}ms") return result }