gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/persist.go (about) 1 package renter 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "time" 8 9 "gitlab.com/NebulousLabs/errors" 10 "gitlab.com/NebulousLabs/writeaheadlog" 11 12 "gitlab.com/SkynetLabs/skyd/build" 13 "gitlab.com/SkynetLabs/skyd/skymodules" 14 "gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem" 15 "gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem/siafile" 16 "go.sia.tech/siad/persist" 17 "go.sia.tech/siad/types" 18 ) 19 20 // PersistedStats contains the information about the renter's stats which is 21 // persisted to disk. 22 type PersistedStats struct { 23 RegistryReadStats skymodules.PersistedDistributionTracker `json:"registryreadstats"` 24 RegistryWriteStats skymodules.PersistedDistributionTracker `json:"registrywritestats"` 25 BaseSectorUploadStats skymodules.PersistedDistributionTracker `json:"basesectoruploadstats"` 26 ChunkUploadStats skymodules.PersistedDistributionTracker `json:"chunkuploadstats"` 27 StreamBufferStats skymodules.PersistedDistributionTracker `json:"streambufferstats"` 28 } 29 30 const ( 31 logFile = skymodules.RenterDir + ".log" 32 repairLogFile = "repair.log" 33 // PersistFilename is the filename to be used when persisting renter 34 // information to a JSON file 35 PersistFilename = "renter.json" 36 // SiaDirMetadata is the name of the metadata file for the sia directory 37 SiaDirMetadata = ".siadir" 38 // StatsFilename is the name of the file persisting the stats of the 39 // renter. 40 StatsFilename = "stats.json" 41 // walFile is the filename of the renter's writeaheadlog's file. 42 walFile = skymodules.RenterDir + ".wal" 43 ) 44 45 var ( 46 // ErrBadFile is an error when a file does not qualify as .sia file 47 ErrBadFile = errors.New("not a .sia file") 48 // ErrIncompatible is an error when file is not compatible with current 49 // version 50 ErrIncompatible = errors.New("file is not compatible with current version") 51 // ErrNoNicknames is an error when no nickname is given 52 ErrNoNicknames = errors.New("at least one nickname must be supplied") 53 // ErrNonShareSuffix is an error when the suffix of a file does not match 54 // the defined share extension 55 ErrNonShareSuffix = errors.New("suffix of file must be " + skymodules.SiaFileExtension) 56 57 settingsMetadata = persist.Metadata{ 58 Header: "Renter Persistence", 59 Version: persistVersion, 60 } 61 62 shareHeader = [15]byte{'S', 'i', 'a', ' ', 'S', 'h', 'a', 'r', 'e', 'd', ' ', 'F', 'i', 'l', 'e'} 63 shareVersion = "0.4" 64 65 // statsPersistInterval defines the interval the renter uses for 66 // persisting its stats. 67 statsPersistInterval = build.Select(build.Var{ 68 Dev: time.Minute, 69 Standard: 30 * time.Minute, 70 Testing: 2 * time.Second, 71 }).(time.Duration) 72 73 // statsMetadata is the metadata used when persisting the renter stats. 74 statsMetadata = persist.Metadata{ 75 Header: "Stats", 76 Version: "1.5.7", 77 } 78 79 // Persist Version Numbers 80 persistVersion040 = "0.4" 81 persistVersion133 = "1.3.3" 82 persistVersion140 = "1.4.0" 83 persistVersion142 = "1.4.2" 84 ) 85 86 type ( 87 // persist contains all of the persistent renter data. 88 persistence struct { 89 MaxDownloadSpeed int64 90 MaxUploadSpeed int64 91 UploadedBackups []skymodules.UploadedBackup 92 SyncedContracts []types.FileContractID 93 } 94 ) 95 96 // saveSync stores the current renter data to disk and then syncs to disk. 97 func (r *Renter) saveSync() error { 98 return persist.SaveJSON(settingsMetadata, r.persist, filepath.Join(r.persistDir, PersistFilename)) 99 } 100 101 // threadedStatsPersister periodically persists the renter's collected stats. 102 func (r *Renter) threadedStatsPersister() { 103 if err := r.tg.Add(); err != nil { 104 return 105 } 106 defer r.tg.Done() 107 108 ticker := time.NewTicker(statsPersistInterval) 109 for { 110 statsPath := filepath.Join(r.persistDir, StatsFilename) 111 err := persist.SaveJSON(statsMetadata, PersistedStats{ 112 RegistryReadStats: r.staticRegistryReadStats.Persist(), 113 RegistryWriteStats: r.staticRegWriteStats.Persist(), 114 BaseSectorUploadStats: r.staticBaseSectorUploadStats.Persist(), 115 ChunkUploadStats: r.staticChunkUploadStats.Persist(), 116 StreamBufferStats: r.staticStreamBufferStats.Persist(), 117 }, statsPath) 118 if err != nil { 119 r.staticLog.Print("Failed to persist stats object:", err) 120 } 121 122 // Sleep 123 select { 124 case <-r.tg.StopCtx().Done(): 125 return // shutdown 126 case <-ticker.C: 127 } 128 } 129 } 130 131 // managedLoadSettings fetches the saved renter data from disk. 132 func (r *Renter) managedLoadSettings() error { 133 r.persist = persistence{} 134 err := persist.LoadJSON(settingsMetadata, &r.persist, filepath.Join(r.persistDir, PersistFilename)) 135 if os.IsNotExist(err) { 136 // No persistence yet, set the defaults and continue. 137 r.persist.MaxDownloadSpeed = DefaultMaxDownloadSpeed 138 r.persist.MaxUploadSpeed = DefaultMaxUploadSpeed 139 id := r.mu.Lock() 140 err = r.saveSync() 141 r.mu.Unlock(id) 142 if err != nil { 143 return err 144 } 145 } else if errors.Contains(err, persist.ErrBadVersion) { 146 // Outdated version, try the 040 to 133 upgrade. 147 err = convertPersistVersionFrom040To133(filepath.Join(r.persistDir, PersistFilename)) 148 if err != nil { 149 r.staticLog.Println("WARNING: 040 to 133 renter upgrade failed, trying 133 to 140 next", err) 150 } 151 // Then upgrade from 133 to 140. 152 oldContracts := r.staticHostContractor.OldContracts() 153 err = r.convertPersistVersionFrom133To140(filepath.Join(r.persistDir, PersistFilename), oldContracts) 154 if err != nil { 155 r.staticLog.Println("WARNING: 133 to 140 renter upgrade failed", err) 156 } 157 // Then upgrade from 140 to 142. 158 err = r.convertPersistVersionFrom140To142(filepath.Join(r.persistDir, PersistFilename)) 159 if err != nil { 160 r.staticLog.Println("WARNING: 140 to 142 renter upgrade failed", err) 161 // Nothing left to try. 162 return err 163 } 164 r.staticLog.Println("Renter upgrade successful") 165 // Re-load the settings now that the file has been upgraded. 166 return r.managedLoadSettings() 167 } else if err != nil { 168 return err 169 } 170 171 // Set the bandwidth limits on the contractor, which was already initialized 172 // without bandwidth limits. 173 return r.staticSetBandwidthLimits(r.persist.MaxDownloadSpeed, r.persist.MaxUploadSpeed) 174 } 175 176 // managedInitPersist handles all of the persistence initialization, such as creating 177 // the persistence directory and starting the logger. 178 func (r *Renter) managedInitPersist() error { 179 // Create the persist and filesystem directories if they do not yet exist. 180 // 181 // Note: the os package needs to be used here instead of the renter's 182 // CreateDir method because the staticDirSet has not been initialized yet. 183 // The directory is needed before the staticDirSet can be initialized 184 // because the wal needs the directory to be created and the staticDirSet 185 // needs the wal. 186 fsRoot := filepath.Join(r.persistDir, skymodules.FileSystemRoot) 187 err := os.MkdirAll(fsRoot, skymodules.DefaultDirPerm) 188 if err != nil { 189 return err 190 } 191 192 // Initialize the writeaheadlog. 193 options := writeaheadlog.Options{ 194 StaticLog: r.staticLog.Logger, 195 Path: filepath.Join(r.persistDir, walFile), 196 } 197 txns, wal, err := writeaheadlog.NewWithOptions(options) 198 if err != nil { 199 return err 200 } 201 if err := r.tg.AfterStop(wal.Close); err != nil { 202 return err 203 } 204 205 // Apply unapplied wal txns before loading the persistence structure to 206 // avoid loading potentially corrupted files. 207 if len(txns) > 0 { 208 r.staticLog.Println("Wal initialized", len(txns), "transactions to apply") 209 } 210 for _, txn := range txns { 211 applyTxn := true 212 r.staticLog.Println("applying transaction with", len(txn.Updates), "updates") 213 for _, update := range txn.Updates { 214 if siafile.IsSiaFileUpdate(update) { 215 r.staticLog.Println("Applying a siafile update:", update.Name) 216 if err := siafile.ApplyUpdates(update); err != nil { 217 return errors.AddContext(err, "failed to apply SiaFile update") 218 } 219 } else { 220 r.staticLog.Println("wal update not applied, marking transaction as not applied") 221 applyTxn = false 222 } 223 } 224 if applyTxn { 225 if err := txn.SignalUpdatesApplied(); err != nil { 226 return err 227 } 228 } 229 } 230 231 // Create the filesystem. 232 fs, err := filesystem.New(fsRoot, r.staticLog, wal) 233 if err != nil { 234 return err 235 } 236 237 // Initialize the wal, staticFileSet and the staticDirSet. With the 238 // staticDirSet finish the initialization of the files directory 239 r.staticWAL = wal 240 r.staticFileSystem = fs 241 242 // Load the prior persistence structures. 243 if err := r.managedLoadSettings(); err != nil { 244 return errors.AddContext(err, "failed to load renter's persistence structrue") 245 } 246 247 // Load the stats. 248 if err := r.managedInitStats(); err != nil { 249 return errors.AddContext(err, "failed to initialize the renter's distribution trackers") 250 } 251 252 // Create the essential dirs in the filesystem. 253 err = fs.NewSiaDir(skymodules.HomeFolder, skymodules.DefaultDirPerm) 254 if err != nil && !errors.Contains(err, filesystem.ErrExists) { 255 return err 256 } 257 err = fs.NewSiaDir(skymodules.UserFolder, skymodules.DefaultDirPerm) 258 if err != nil && !errors.Contains(err, filesystem.ErrExists) { 259 return err 260 } 261 err = fs.NewSiaDir(skymodules.BackupFolder, skymodules.DefaultDirPerm) 262 if err != nil && !errors.Contains(err, filesystem.ErrExists) { 263 return err 264 } 265 err = fs.NewSiaDir(skymodules.SkynetFolder, skymodules.DefaultDirPerm) 266 if err != nil && !errors.Contains(err, filesystem.ErrExists) { 267 return err 268 } 269 return nil 270 } 271 272 // managedInitStats initializes the distribution trackers of the renter. 273 func (r *Renter) managedInitStats() error { 274 // Init the trackers. 275 r.staticRegistryReadStats = skymodules.NewDistributionTrackerStandard() 276 r.staticRegWriteStats = skymodules.NewDistributionTrackerStandard() 277 r.staticBaseSectorUploadStats = skymodules.NewDistributionTrackerStandard() 278 r.staticChunkUploadStats = skymodules.NewDistributionTrackerStandard() 279 r.staticStreamBufferStats = skymodules.NewDistributionTrackerStandard() 280 281 // Load the existing stats. 282 statsPath := filepath.Join(r.persistDir, StatsFilename) 283 var stats PersistedStats 284 err := persist.LoadJSON(statsMetadata, &stats, statsPath) 285 if os.IsNotExist(err) { 286 // No persistence yet. Seed the trackers. 287 r.staticRegistryReadStats.AddDataPoint(readRegistryStatsSeed) // Seed the stats so that startup doesn't say 0. 288 r.staticRegWriteStats.AddDataPoint(5 * time.Second) // Seed the stats so that startup doesn't say 0. 289 r.staticBaseSectorUploadStats.AddDataPoint(15 * time.Second) // Seed the stats so that startup doesn't say 0. 290 r.staticChunkUploadStats.AddDataPoint(15 * time.Second) // Seed the stats so that startup doesn't say 0. 291 r.staticStreamBufferStats.AddDataPoint(5 * time.Second) // Seed the stats so that startup doesn't say 0. 292 return nil 293 } else if err != nil { 294 if build.Release == "testing" { 295 build.Critical(err) 296 } 297 fmt.Println("WARN: reset stats after failing to load them", err) 298 return nil // ignore and overwrite 299 } 300 301 // Found stats. Seed with existing values. 302 err1 := r.staticRegistryReadStats.Load(stats.RegistryReadStats) 303 err2 := r.staticRegWriteStats.Load(stats.RegistryWriteStats) 304 err3 := r.staticBaseSectorUploadStats.Load(stats.BaseSectorUploadStats) 305 err4 := r.staticChunkUploadStats.Load(stats.ChunkUploadStats) 306 err5 := r.staticStreamBufferStats.Load(stats.StreamBufferStats) 307 if err := errors.Compose(err1, err2, err3, err4, err5); err != nil { 308 if build.Release == "testing" { 309 build.Critical(err) 310 } 311 fmt.Println("WARN: failed to load one or more distribution trackers") 312 return nil // ignore and overwrite 313 } 314 return nil 315 }