use serde::Deserialize;
use std::fmt;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GradleBuildCachePerformance {
pub id: String,
pub build_time: i64,
pub effective_task_execution_time: i64,
pub effective_work_unit_execution_time: i64,
pub serial_task_execution_time: i64,
pub serial_work_unit_execution_time: i64,
pub serialization_factor: f64,
pub task_execution: Vec<TaskExecutionEntry>,
pub task_fingerprinting_summary: Option<FingerprintingSummary>,
pub work_unit_fingerprinting_summary: Option<FingerprintingSummary>,
#[allow(dead_code)]
pub avoidance_savings_summary: AvoidanceSavingsSummary,
pub task_avoidance_savings_summary: AvoidanceSavingsSummary,
pub work_unit_avoidance_savings_summary: AvoidanceSavingsSummary,
pub build_caches: Option<BuildCaches>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TaskExecutionEntry {
pub task_path: String,
pub task_type: String,
pub avoidance_outcome: AvoidanceOutcome,
pub duration: i64,
pub fingerprinting_duration: Option<i64>,
pub avoidance_savings: Option<i64>,
pub non_cacheability_category: Option<NonCacheabilityCategory>,
pub non_cacheability_reason: Option<String>,
pub skip_reason_message: Option<String>,
pub cache_artifact_size: Option<i64>,
pub cache_artifact_rejected_reason: Option<CacheArtifactRejectedReason>,
pub cache_key: Option<String>,
pub has_failed: bool,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum AvoidanceOutcome {
AvoidedUpToDate,
AvoidedFromLocalCache,
AvoidedFromRemoteCache,
ExecutedCacheable,
ExecutedNotCacheable,
ExecutedUnknownCacheability,
AvoidedUnknownReason,
Lifecycle,
#[serde(rename = "no-source")]
NoSource,
Skipped,
}
impl fmt::Display for AvoidanceOutcome {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AvoidanceOutcome::AvoidedUpToDate => write!(f, "UP-TO-DATE"),
AvoidanceOutcome::AvoidedFromLocalCache => write!(f, "FROM-CACHE (local)"),
AvoidanceOutcome::AvoidedFromRemoteCache => write!(f, "FROM-CACHE (remote)"),
AvoidanceOutcome::ExecutedCacheable => write!(f, "EXECUTED (cacheable)"),
AvoidanceOutcome::ExecutedNotCacheable => write!(f, "EXECUTED (not cacheable)"),
AvoidanceOutcome::ExecutedUnknownCacheability => {
write!(f, "EXECUTED (unknown cacheability)")
}
AvoidanceOutcome::AvoidedUnknownReason => write!(f, "AVOIDED (unknown)"),
AvoidanceOutcome::Lifecycle => write!(f, "LIFECYCLE"),
AvoidanceOutcome::NoSource => write!(f, "NO-SOURCE"),
AvoidanceOutcome::Skipped => write!(f, "SKIPPED"),
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum NonCacheabilityCategory {
BuildCacheNotEnabled,
#[serde(rename = "cache-if_condition_not_matched")]
CacheIfConditionNotMatched,
DisabledToEnsureCorrectness,
#[serde(rename = "do-not-cache-if_condition_matched")]
DoNotCacheIfConditionMatched,
MultipleOutputsDeclared,
NoOutputsDeclared,
NonCacheableInputs,
NonCacheableTaskAction,
NonCacheableTaskImplementation,
NonCacheableTreeOutput,
OverlappingOutputs,
TaskHasNoActions,
TaskOutputCachingNotEnabled,
Unknown,
}
impl fmt::Display for NonCacheabilityCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NonCacheabilityCategory::BuildCacheNotEnabled => write!(f, "build cache not enabled"),
NonCacheabilityCategory::CacheIfConditionNotMatched => {
write!(f, "cache-if condition not matched")
}
NonCacheabilityCategory::DisabledToEnsureCorrectness => {
write!(f, "disabled to ensure correctness")
}
NonCacheabilityCategory::DoNotCacheIfConditionMatched => {
write!(f, "do-not-cache-if condition matched")
}
NonCacheabilityCategory::MultipleOutputsDeclared => {
write!(f, "multiple outputs declared")
}
NonCacheabilityCategory::NoOutputsDeclared => write!(f, "no outputs declared"),
NonCacheabilityCategory::NonCacheableInputs => write!(f, "non-cacheable inputs"),
NonCacheabilityCategory::NonCacheableTaskAction => {
write!(f, "non-cacheable task action")
}
NonCacheabilityCategory::NonCacheableTaskImplementation => {
write!(f, "non-cacheable task implementation")
}
NonCacheabilityCategory::NonCacheableTreeOutput => {
write!(f, "non-cacheable tree output")
}
NonCacheabilityCategory::OverlappingOutputs => write!(f, "overlapping outputs"),
NonCacheabilityCategory::TaskHasNoActions => write!(f, "task has no actions"),
NonCacheabilityCategory::TaskOutputCachingNotEnabled => {
write!(f, "task output caching not enabled")
}
NonCacheabilityCategory::Unknown => write!(f, "unknown"),
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum CacheArtifactRejectedReason {
ArtifactExceedsMaximumSize,
Unknown,
}
impl fmt::Display for CacheArtifactRejectedReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CacheArtifactRejectedReason::ArtifactExceedsMaximumSize => {
write!(f, "artifact exceeds maximum size")
}
CacheArtifactRejectedReason::Unknown => write!(f, "unknown"),
}
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AvoidanceSavingsSummary {
pub total: i64,
pub ratio: f64,
pub up_to_date: i64,
pub local_build_cache: i64,
pub remote_build_cache: i64,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FingerprintingSummary {
pub count: i32,
pub serial_duration: i64,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BuildCaches {
pub local: Option<LocalBuildCache>,
pub remote: Option<RemoteBuildCache>,
pub overhead: Option<CacheOverhead>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LocalBuildCache {
pub is_enabled: bool,
pub is_push_enabled: Option<bool>,
pub is_store_enabled_for_remote_build_cache: Option<bool>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RemoteBuildCache {
pub is_enabled: bool,
pub is_push_enabled: Option<bool>,
pub url: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CacheOverhead {
pub uploading: i64,
pub downloading: i64,
pub packing: i64,
pub unpacking: i64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_avoidance_outcome_display() {
assert_eq!(AvoidanceOutcome::AvoidedUpToDate.to_string(), "UP-TO-DATE");
assert_eq!(
AvoidanceOutcome::AvoidedFromLocalCache.to_string(),
"FROM-CACHE (local)"
);
assert_eq!(
AvoidanceOutcome::AvoidedFromRemoteCache.to_string(),
"FROM-CACHE (remote)"
);
assert_eq!(
AvoidanceOutcome::ExecutedCacheable.to_string(),
"EXECUTED (cacheable)"
);
assert_eq!(
AvoidanceOutcome::ExecutedNotCacheable.to_string(),
"EXECUTED (not cacheable)"
);
assert_eq!(AvoidanceOutcome::Lifecycle.to_string(), "LIFECYCLE");
assert_eq!(AvoidanceOutcome::NoSource.to_string(), "NO-SOURCE");
assert_eq!(AvoidanceOutcome::Skipped.to_string(), "SKIPPED");
}
#[test]
fn test_non_cacheability_category_display() {
assert_eq!(
NonCacheabilityCategory::BuildCacheNotEnabled.to_string(),
"build cache not enabled"
);
assert_eq!(
NonCacheabilityCategory::TaskOutputCachingNotEnabled.to_string(),
"task output caching not enabled"
);
assert_eq!(NonCacheabilityCategory::Unknown.to_string(), "unknown");
}
#[test]
fn test_avoidance_outcome_deserialize() {
let json = r#""avoided_up_to_date""#;
let outcome: AvoidanceOutcome = serde_json::from_str(json).unwrap();
assert_eq!(outcome, AvoidanceOutcome::AvoidedUpToDate);
let json = r#""no-source""#;
let outcome: AvoidanceOutcome = serde_json::from_str(json).unwrap();
assert_eq!(outcome, AvoidanceOutcome::NoSource);
let json = r#""executed_cacheable""#;
let outcome: AvoidanceOutcome = serde_json::from_str(json).unwrap();
assert_eq!(outcome, AvoidanceOutcome::ExecutedCacheable);
}
#[test]
fn test_non_cacheability_category_deserialize() {
let json = r#""cache-if_condition_not_matched""#;
let cat: NonCacheabilityCategory = serde_json::from_str(json).unwrap();
assert_eq!(cat, NonCacheabilityCategory::CacheIfConditionNotMatched);
let json = r#""do-not-cache-if_condition_matched""#;
let cat: NonCacheabilityCategory = serde_json::from_str(json).unwrap();
assert_eq!(cat, NonCacheabilityCategory::DoNotCacheIfConditionMatched);
}
#[test]
fn test_task_execution_entry_deserialize() {
let json = r#"{
"taskPath": ":app:compileJava",
"taskType": "org.gradle.api.tasks.compile.JavaCompile",
"avoidanceOutcome": "executed_cacheable",
"duration": 1234,
"fingerprintingDuration": 100,
"avoidanceSavings": null,
"nonCacheabilityCategory": null,
"nonCacheabilityReason": null,
"skipReasonMessage": null,
"cacheArtifactSize": 5678,
"cacheArtifactRejectedReason": null,
"cacheKey": "abc123",
"hasFailed": false
}"#;
let entry: TaskExecutionEntry = serde_json::from_str(json).unwrap();
assert_eq!(entry.task_path, ":app:compileJava");
assert_eq!(entry.task_type, "org.gradle.api.tasks.compile.JavaCompile");
assert_eq!(entry.avoidance_outcome, AvoidanceOutcome::ExecutedCacheable);
assert_eq!(entry.duration, 1234);
assert_eq!(entry.fingerprinting_duration, Some(100));
assert_eq!(entry.avoidance_savings, None);
assert_eq!(entry.cache_artifact_size, Some(5678));
assert_eq!(entry.cache_key, Some("abc123".to_string()));
assert!(!entry.has_failed);
}
#[test]
fn test_avoidance_savings_summary_deserialize() {
let json = r#"{
"total": 5000,
"ratio": 0.42,
"upToDate": 3000,
"localBuildCache": 1500,
"remoteBuildCache": 500
}"#;
let summary: AvoidanceSavingsSummary = serde_json::from_str(json).unwrap();
assert_eq!(summary.total, 5000);
assert!((summary.ratio - 0.42).abs() < 0.001);
assert_eq!(summary.up_to_date, 3000);
assert_eq!(summary.local_build_cache, 1500);
assert_eq!(summary.remote_build_cache, 500);
}
}