github.com/anuvu/nomad@v0.8.7-atom1/client/driver/docker_progress.go (about) 1 package driver 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/docker/docker/pkg/jsonmessage" 14 units "github.com/docker/go-units" 15 ) 16 17 const ( 18 // dockerPullActivityDeadline is the default value set in the imageProgressManager 19 // when newImageProgressManager is called 20 dockerPullActivityDeadline = 2 * time.Minute 21 22 // dockerImageProgressReportInterval is the default value set in the 23 // imageProgressManager when newImageProgressManager is called 24 dockerImageProgressReportInterval = 10 * time.Second 25 26 // dockerImageSlowProgressReportInterval is the default value set in the 27 // imageProgressManager when newImageProgressManager is called 28 dockerImageSlowProgressReportInterval = 2 * time.Minute 29 ) 30 31 // layerProgress tracks the state and downloaded bytes of a single layer within 32 // a docker image 33 type layerProgress struct { 34 id string 35 status layerProgressStatus 36 currentBytes int64 37 totalBytes int64 38 } 39 40 type layerProgressStatus int 41 42 const ( 43 layerProgressStatusUnknown layerProgressStatus = iota 44 layerProgressStatusStarting 45 layerProgressStatusWaiting 46 layerProgressStatusDownloading 47 layerProgressStatusVerifying 48 layerProgressStatusDownloaded 49 layerProgressStatusExtracting 50 layerProgressStatusComplete 51 layerProgressStatusExists 52 ) 53 54 func lpsFromString(status string) layerProgressStatus { 55 switch status { 56 case "Pulling fs layer": 57 return layerProgressStatusStarting 58 case "Waiting": 59 return layerProgressStatusWaiting 60 case "Downloading": 61 return layerProgressStatusDownloading 62 case "Verifying Checksum": 63 return layerProgressStatusVerifying 64 case "Download complete": 65 return layerProgressStatusDownloaded 66 case "Extracting": 67 return layerProgressStatusExtracting 68 case "Pull complete": 69 return layerProgressStatusComplete 70 case "Already exists": 71 return layerProgressStatusExists 72 default: 73 return layerProgressStatusUnknown 74 } 75 } 76 77 // imageProgress tracks the status of each child layer as its pulled from a 78 // docker image repo 79 type imageProgress struct { 80 sync.RWMutex 81 lastMessage *jsonmessage.JSONMessage 82 timestamp time.Time 83 layers map[string]*layerProgress 84 pullStart time.Time 85 } 86 87 // get returns a status message and the timestamp of the last status update 88 func (p *imageProgress) get() (string, time.Time) { 89 p.RLock() 90 defer p.RUnlock() 91 92 if p.lastMessage == nil { 93 return "No progress", p.timestamp 94 } 95 96 var pulled, pulling, waiting int 97 for _, l := range p.layers { 98 switch { 99 case l.status == layerProgressStatusStarting || 100 l.status == layerProgressStatusWaiting: 101 waiting++ 102 case l.status == layerProgressStatusDownloading || 103 l.status == layerProgressStatusVerifying: 104 pulling++ 105 case l.status >= layerProgressStatusDownloaded: 106 pulled++ 107 } 108 } 109 110 elapsed := time.Now().Sub(p.pullStart) 111 cur := p.currentBytes() 112 total := p.totalBytes() 113 var est int64 114 if cur != 0 { 115 est = (elapsed.Nanoseconds() / cur * total) - elapsed.Nanoseconds() 116 } 117 118 var msg strings.Builder 119 fmt.Fprintf(&msg, "Pulled %d/%d (%s/%s) layers: %d waiting/%d pulling", 120 pulled, len(p.layers), units.BytesSize(float64(cur)), units.BytesSize(float64(total)), 121 waiting, pulling) 122 123 if est > 0 { 124 fmt.Fprintf(&msg, " - est %.1fs remaining", time.Duration(est).Seconds()) 125 } 126 return msg.String(), p.timestamp 127 } 128 129 // set takes a status message received from the docker engine api during an image 130 // pull and updates the status of the corresponding layer 131 func (p *imageProgress) set(msg *jsonmessage.JSONMessage) { 132 p.Lock() 133 defer p.Unlock() 134 135 p.lastMessage = msg 136 p.timestamp = time.Now() 137 138 lps := lpsFromString(msg.Status) 139 if lps == layerProgressStatusUnknown { 140 return 141 } 142 143 layer, ok := p.layers[msg.ID] 144 if !ok { 145 layer = &layerProgress{id: msg.ID} 146 p.layers[msg.ID] = layer 147 } 148 layer.status = lps 149 if msg.Progress != nil && lps == layerProgressStatusDownloading { 150 layer.currentBytes = msg.Progress.Current 151 layer.totalBytes = msg.Progress.Total 152 } else if lps == layerProgressStatusDownloaded { 153 layer.currentBytes = layer.totalBytes 154 } 155 } 156 157 // currentBytes iterates through all image layers and sums the total of 158 // current bytes. The caller is responsible for acquiring a read lock on the 159 // imageProgress struct 160 func (p *imageProgress) currentBytes() int64 { 161 var b int64 162 for _, l := range p.layers { 163 b += l.currentBytes 164 } 165 return b 166 } 167 168 // totalBytes iterates through all image layers and sums the total of 169 // total bytes. The caller is responsible for acquiring a read lock on the 170 // imageProgress struct 171 func (p *imageProgress) totalBytes() int64 { 172 var b int64 173 for _, l := range p.layers { 174 b += l.totalBytes 175 } 176 return b 177 } 178 179 // progressReporterFunc defines the method for handling inactivity and report 180 // events from the imageProgressManager. The image name, current status message 181 // and timestamp of last received status update are passed in. 182 type progressReporterFunc func(image string, msg string, timestamp time.Time) 183 184 // imageProgressManager tracks the progress of pulling a docker image from an 185 // image repository. 186 // It also implemented the io.Writer interface so as to be passed to the docker 187 // client pull image method in order to receive status updates from the docker 188 // engine api. 189 type imageProgressManager struct { 190 imageProgress *imageProgress 191 image string 192 activityDeadline time.Duration 193 inactivityFunc progressReporterFunc 194 reportInterval time.Duration 195 reporter progressReporterFunc 196 slowReportInterval time.Duration 197 slowReporter progressReporterFunc 198 lastSlowReport time.Time 199 cancel context.CancelFunc 200 stopCh chan struct{} 201 buf bytes.Buffer 202 } 203 204 func newImageProgressManager( 205 image string, cancel context.CancelFunc, 206 inactivityFunc, reporter, slowReporter progressReporterFunc) *imageProgressManager { 207 208 pm := &imageProgressManager{ 209 image: image, 210 activityDeadline: dockerPullActivityDeadline, 211 inactivityFunc: inactivityFunc, 212 reportInterval: dockerImageProgressReportInterval, 213 reporter: reporter, 214 slowReportInterval: dockerImageSlowProgressReportInterval, 215 slowReporter: slowReporter, 216 imageProgress: &imageProgress{ 217 timestamp: time.Now(), 218 layers: make(map[string]*layerProgress), 219 }, 220 cancel: cancel, 221 stopCh: make(chan struct{}), 222 } 223 224 pm.start() 225 return pm 226 } 227 228 // start intiates the ticker to trigger the inactivity and reporter handlers 229 func (pm *imageProgressManager) start() { 230 now := time.Now() 231 pm.imageProgress.pullStart = now 232 pm.lastSlowReport = now 233 go func() { 234 ticker := time.NewTicker(dockerImageProgressReportInterval) 235 for { 236 select { 237 case <-ticker.C: 238 msg, lastStatusTime := pm.imageProgress.get() 239 t := time.Now() 240 if t.Sub(lastStatusTime) > pm.activityDeadline { 241 pm.inactivityFunc(pm.image, msg, lastStatusTime) 242 pm.cancel() 243 return 244 } 245 if t.Sub(pm.lastSlowReport) > pm.slowReportInterval { 246 pm.slowReporter(pm.image, msg, lastStatusTime) 247 pm.lastSlowReport = t 248 } 249 pm.reporter(pm.image, msg, lastStatusTime) 250 case <-pm.stopCh: 251 return 252 } 253 } 254 }() 255 } 256 257 func (pm *imageProgressManager) stop() { 258 close(pm.stopCh) 259 } 260 261 func (pm *imageProgressManager) Write(p []byte) (n int, err error) { 262 n, err = pm.buf.Write(p) 263 var msg jsonmessage.JSONMessage 264 265 for { 266 line, err := pm.buf.ReadBytes('\n') 267 if err == io.EOF { 268 // Partial write of line; push back onto buffer and break until full line 269 pm.buf.Write(line) 270 break 271 } 272 if err != nil { 273 return n, err 274 } 275 err = json.Unmarshal(line, &msg) 276 if err != nil { 277 return n, err 278 } 279 280 if msg.Error != nil { 281 // error received from the docker engine api 282 return n, msg.Error 283 } 284 285 pm.imageProgress.set(&msg) 286 } 287 288 return 289 }