| OLD | NEW |
| 1 /* | 1 /* |
| 2 Copyright 2016 The LUCI Authors. All rights reserved. | 2 Copyright 2016 The LUCI Authors. All rights reserved. |
| 3 Use of this source code is governed under the Apache License, Version 2.0 | 3 Use of this source code is governed under the Apache License, Version 2.0 |
| 4 that can be found in the LICENSE file. | 4 that can be found in the LICENSE file. |
| 5 */ | 5 */ |
| 6 | 6 |
| 7 ///<reference path="../logdog-stream/logdog.ts" /> | 7 ///<reference path="../logdog-stream/logdog.ts" /> |
| 8 ///<reference path="../luci-operation/operation.ts" /> | 8 ///<reference path="../luci-operation/operation.ts" /> |
| 9 ///<reference path="../luci-sleep-promise/promise.ts" /> | 9 ///<reference path="../luci-sleep-promise/promise.ts" /> |
| 10 ///<reference path="../rpc/client.ts" /> | 10 ///<reference path="../rpc/client.ts" /> |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 53 */ | 53 */ |
| 54 TAIL, | 54 TAIL, |
| 55 /** | 55 /** |
| 56 * Represents an anchor point where the split occurred, obtained through a | 56 * Represents an anchor point where the split occurred, obtained through a |
| 57 * single "Tail()" RPC call. If the terminal index is known when the split | 57 * single "Tail()" RPC call. If the terminal index is known when the split |
| 58 * occurs, this should be the terminal index. | 58 * occurs, this should be the terminal index. |
| 59 */ | 59 */ |
| 60 BOTTOM, | 60 BOTTOM, |
| 61 } | 61 } |
| 62 | 62 |
| 63 /** | |
| 64 * Interface of the specific Model functions used by the view. This is | |
| 65 * explicitly listed here as a reference for the functions that the | |
| 66 * model-viewer interface calls. See the Model class for method details. | |
| 67 * | |
| 68 * These methods are used to control the Model state and to nofity the Model | |
| 69 * of external events that occur. | |
| 70 */ | |
| 71 interface ModelInterface { | |
| 72 fetch(cancel: boolean): Promise<void>; | |
| 73 split(): Promise<void>; | |
| 74 | |
| 75 reset(): void; | |
| 76 setAutomatic(v: boolean): void; | |
| 77 setFetchFromTail(v: boolean): void; | |
| 78 notifyAuthenticationChanged(): void; | |
| 79 } | |
| 80 | |
| 81 /** A log loading profile type. */ | 63 /** A log loading profile type. */ |
| 82 type ModelProfile = { | 64 type ModelProfile = { |
| 83 /** The size of the first fetch. */ | 65 /** The size of the first fetch. */ |
| 84 initialFetchSize: number; | 66 initialFetchSize: number; |
| 85 /** The size of the first fetch, if loading multiple streams. */ | 67 /** The size of the first fetch, if loading multiple streams. */ |
| 86 multiInitialFetchSize: number; | 68 multiInitialFetchSize: number; |
| 87 /** The size of each subsequent fetch. */ | 69 /** The size of each subsequent fetch. */ |
| 88 fetchSize: number; | 70 fetchSize: number; |
| 89 }; | 71 }; |
| 90 | 72 |
| 91 /** | 73 /** |
| 92 * Model manages stream loading. | 74 * Model manages stream loading. |
| 93 * | 75 * |
| 94 * Model exports features to the user interface by conforming to | |
| 95 * ModelInterface. All Polymer Model functions must be documented in that | |
| 96 * interface. | |
| 97 * | |
| 98 * Model pushes state update to the user interface with the ViewBinding that | 76 * Model pushes state update to the user interface with the ViewBinding that |
| 99 * is provided to it on construction. | 77 * is provided to it on construction. |
| 100 * | 78 * |
| 101 * Model represents a view of the loading state of the configured log streams. | 79 * Model represents a view of the loading state of the configured log streams. |
| 102 * Log streams are individual named sets of consecutive records that are | 80 * Log streams are individual named sets of consecutive records that are |
| 103 * either streaming (no known terminal index, so the expectation is that new | 81 * either streaming (no known terminal index, so the expectation is that new |
| 104 * log records are being generated) or finished (known terminal index). The | 82 * log records are being generated) or finished (known terminal index). The |
| 105 * Model's job is to understand the state of these streams, load their data, | 83 * Model's job is to understand the state of these streams, load their data, |
| 106 * and cause it to be output to the user interface. | 84 * and cause it to be output to the user interface. |
| 107 * | 85 * |
| (...skipping 20 matching lines...) Expand all Loading... |
| 128 * | 106 * |
| 129 * If the HEAD pointer ever reaches the TAIL pointer, all logs between 0 and | 107 * If the HEAD pointer ever reaches the TAIL pointer, all logs between 0 and |
| 130 * SPLIT have been filled in and the fetching situation turns back into a | 108 * SPLIT have been filled in and the fetching situation turns back into a |
| 131 * standard sequential fetch. | 109 * standard sequential fetch. |
| 132 * | 110 * |
| 133 * This scheme is designed around the capabilities of the LogDog log API. | 111 * This scheme is designed around the capabilities of the LogDog log API. |
| 134 * | 112 * |
| 135 * The view may optionally expose itself as "mobile", indicating to the Model | 113 * The view may optionally expose itself as "mobile", indicating to the Model |
| 136 * that it should use mobile-friendly tuning parameters. | 114 * that it should use mobile-friendly tuning parameters. |
| 137 */ | 115 */ |
| 138 export class Model implements ModelInterface { | 116 export class Model { |
| 139 /** Default single-stream profile. */ | 117 /** Default single-stream profile. */ |
| 140 static DEFAULT_PROFILE: ModelProfile = { | 118 static DEFAULT_PROFILE: ModelProfile = { |
| 141 initialFetchSize: (1024 * 24), // 24KiB | 119 initialFetchSize: (1024 * 24), // 24KiB |
| 142 multiInitialFetchSize: (1024 * 4), // 4KiB | 120 multiInitialFetchSize: (1024 * 4), // 4KiB |
| 143 fetchSize: (4 * 1024 * 1024), // 4MiB | 121 fetchSize: (4 * 1024 * 1024), // 4MiB |
| 144 }; | 122 }; |
| 145 | 123 |
| 146 /** Profile to use for a mobile device, regardless of stream count. */ | 124 /** Profile to use for a mobile device, regardless of stream count. */ |
| 147 static MOBILE_PROFILE: ModelProfile = { | 125 static MOBILE_PROFILE: ModelProfile = { |
| 148 initialFetchSize: (1024 * 4), // 4KiB | 126 initialFetchSize: (1024 * 4), // 4KiB |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 185 * Retained callback (Promise resolve) to invoke when authentication state | 163 * Retained callback (Promise resolve) to invoke when authentication state |
| 186 * changes. | 164 * changes. |
| 187 */ | 165 */ |
| 188 private authChangedCallback: (() => void); | 166 private authChangedCallback: (() => void); |
| 189 | 167 |
| 190 /** The current fetch Promise. */ | 168 /** The current fetch Promise. */ |
| 191 private currentOperation: luci.Operation|null = null; | 169 private currentOperation: luci.Operation|null = null; |
| 192 private currentFetchPromise: Promise<void>|null = null; | 170 private currentFetchPromise: Promise<void>|null = null; |
| 193 | 171 |
| 194 /** Are we in automatic mode? */ | 172 /** Are we in automatic mode? */ |
| 195 private automatic = false; | 173 private isAutomatic = false; |
| 196 /** Are we tailing? */ | 174 /** Are we tailing? */ |
| 197 private fetchFromTail = false; | 175 private fetchFromTail = false; |
| 198 /** Are we in the middle of rendering logs? */ | 176 /** Are we in the middle of rendering logs? */ |
| 199 private rendering = true; | 177 private rendering = true; |
| 200 | 178 |
| 201 private cachedLogStreamUrl: string|undefined = undefined; | 179 private cachedLogStreamUrl: string|undefined = undefined; |
| 202 | 180 |
| 203 private loadingStateValue: LoadingState = LoadingState.NONE; | 181 private loadingStateValue: LoadingState = LoadingState.NONE; |
| 204 private streamStatusValue: StreamStatusEntry[]; | 182 private streamStatusValue: StreamStatusEntry[]; |
| 205 | 183 |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 272 default: | 250 default: |
| 273 provider = new AggregateLogStream(logStreams); | 251 provider = new AggregateLogStream(logStreams); |
| 274 break; | 252 break; |
| 275 } | 253 } |
| 276 provider.setStreamStatusCallback((st: LogStreamStatus[]) => { | 254 provider.setStreamStatusCallback((st: LogStreamStatus[]) => { |
| 277 if (this.provider === provider) { | 255 if (this.provider === provider) { |
| 278 this.streamStatus = this.buildStreamStatus(st); | 256 this.streamStatus = this.buildStreamStatus(st); |
| 279 } | 257 } |
| 280 }); | 258 }); |
| 281 this.provider = provider; | 259 this.provider = provider; |
| 282 this.loadingState = LoadingState.NONE; | 260 this.setIdleLoadingState(); |
| 261 } |
| 262 |
| 263 private setIdleLoadingState() { |
| 264 this.loadingState = (this.fetchedFullStream) ? (LoadingState.NONE) : |
| 265 (LoadingState.PAUSED); |
| 283 } | 266 } |
| 284 | 267 |
| 285 private resolvePathsIntoStreams(paths: string[]) { | 268 private resolvePathsIntoStreams(paths: string[]) { |
| 286 return Promise.all(paths.map(async path => { | 269 return Promise.all(paths.map(async path => { |
| 287 let stream = LogDog.StreamPath.splitProject(path); | 270 let stream = LogDog.StreamPath.splitProject(path); |
| 288 if (!LogDog.isQuery(stream.path)) { | 271 if (!LogDog.isQuery(stream.path)) { |
| 289 return [stream]; | 272 return [stream]; |
| 290 } | 273 } |
| 291 | 274 |
| 292 // This "path" is really a query. Construct and execute. | 275 // This "path" is really a query. Construct and execute. |
| (...skipping 26 matching lines...) Expand all Loading... |
| 319 * Resets the state. | 302 * Resets the state. |
| 320 */ | 303 */ |
| 321 reset() { | 304 reset() { |
| 322 this.view.clearLogEntries(); | 305 this.view.clearLogEntries(); |
| 323 this.clearCurrentOperation(); | 306 this.clearCurrentOperation(); |
| 324 this.provider = this.nullProvider(); | 307 this.provider = this.nullProvider(); |
| 325 | 308 |
| 326 this.updateControls(); | 309 this.updateControls(); |
| 327 } | 310 } |
| 328 | 311 |
| 312 public get automatic() { |
| 313 return this.isAutomatic; |
| 314 } |
| 315 |
| 316 /** |
| 317 * Sets whether automatic loading is enabled. |
| 318 * |
| 319 * When enabled, a new fetch will immediately be dispatched when a previous |
| 320 * fetch finishes so long as there is still stream data to load. |
| 321 * |
| 322 * This isn't a setter because we need to be able to export it via |
| 323 * interface. |
| 324 */ |
| 325 public set automatic(v: boolean) { |
| 326 this.isAutomatic = v && !this.fetchedFullStream; |
| 327 if (v) { |
| 328 // Passively kick off a new fetch. |
| 329 this.fetch(false); |
| 330 } |
| 331 this.updateControls(); |
| 332 } |
| 333 |
| 329 private nullProvider(): LogProvider { | 334 private nullProvider(): LogProvider { |
| 330 return new AggregateLogStream([]); | 335 return new AggregateLogStream([]); |
| 331 } | 336 } |
| 332 | 337 |
| 333 /** | 338 /** |
| 334 * Clears the current operation, cancelling it if set. If the operation is | 339 * Clears the current operation, cancelling it if set. If the operation is |
| 335 * cleared, the current fetch and rendering states will be reset. | 340 * cleared, the current fetch and rendering states will be reset. |
| 336 * | 341 * |
| 337 * @param op if provided, only cancel the current operation if it equals | 342 * @param op if provided, only cancel the current operation if it equals |
| 338 * the supplied "op". If "op" does not match the current operation, it | 343 * the supplied "op". If "op" does not match the current operation, it |
| 339 * will be cancelled, but the current operation will be left in-tact. | 344 * will be cancelled, but the current operation will be left in-tact. |
| 340 * If "op" is undefined, cancel the current operation regardless. | 345 * If "op" is undefined, cancel the current operation regardless. |
| 341 */ | 346 */ |
| 342 private clearCurrentOperation(op?: luci.Operation) { | 347 clearCurrentOperation(op?: luci.Operation) { |
| 343 if (this.currentOperation) { | 348 if (this.currentOperation) { |
| 344 if (op && op !== this.currentOperation) { | 349 if (op && op !== this.currentOperation) { |
| 345 // Conditional clear, and we are not the current operation, so do | 350 // Conditional clear, and we are not the current operation, so do |
| 346 // nothing. | 351 // nothing. |
| 347 op.cancel(); | 352 op.cancel(); |
| 348 return; | 353 return; |
| 349 } | 354 } |
| 350 this.currentOperation.cancel(); | 355 this.currentOperation.cancel(); |
| 351 this.currentOperation = this.currentFetchPromise = null; | 356 this.currentOperation = this.currentFetchPromise = null; |
| 352 } | 357 } |
| (...skipping 12 matching lines...) Expand all Loading... |
| 365 | 370 |
| 366 private get streamStatus(): StreamStatusEntry[] { | 371 private get streamStatus(): StreamStatusEntry[] { |
| 367 return this.streamStatusValue; | 372 return this.streamStatusValue; |
| 368 } | 373 } |
| 369 private set streamStatus(st: StreamStatusEntry[]) { | 374 private set streamStatus(st: StreamStatusEntry[]) { |
| 370 this.streamStatusValue = st; | 375 this.streamStatusValue = st; |
| 371 this.updateControls(); | 376 this.updateControls(); |
| 372 } | 377 } |
| 373 | 378 |
| 374 private updateControls() { | 379 private updateControls() { |
| 380 let splitState: SplitState; |
| 381 if (this.providerCanSplit) { |
| 382 splitState = SplitState.CAN_SPLIT; |
| 383 } else { |
| 384 splitState = |
| 385 (this.isSplit) ? (SplitState.IS_SPLIT) : (SplitState.CANNOT_SPLIT); |
| 386 } |
| 387 |
| 375 this.view.updateControls({ | 388 this.view.updateControls({ |
| 376 canSplit: this.providerCanSplit, | 389 splitState: splitState, |
| 377 split: this.isSplit, | |
| 378 bottom: !this.fetchedEndOfStream, | |
| 379 fullyLoaded: (this.fetchedFullStream && (!this.rendering)), | 390 fullyLoaded: (this.fetchedFullStream && (!this.rendering)), |
| 380 logStreamUrl: this.logStreamUrl, | 391 logStreamUrl: this.logStreamUrl, |
| 381 loadingState: this.loadingState, | 392 loadingState: this.loadingState, |
| 382 streamStatus: this.streamStatus, | 393 streamStatus: this.streamStatus, |
| 383 }); | 394 }); |
| 384 } | 395 } |
| 385 | 396 |
| 386 /** | 397 /** |
| 387 * Note that the authentication state for the client has changed. This will | 398 * Note that the authentication state for the client has changed. This will |
| 388 * trigger an automatic fetch retry if our previous fetch failed due to | 399 * trigger an automatic fetch retry if our previous fetch failed due to |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 456 /** Fetch logs from an explicit location. */ | 467 /** Fetch logs from an explicit location. */ |
| 457 async fetchLocation(l: Location, cancel: boolean): Promise<void> { | 468 async fetchLocation(l: Location, cancel: boolean): Promise<void> { |
| 458 if (this.currentFetchPromise && (!cancel)) { | 469 if (this.currentFetchPromise && (!cancel)) { |
| 459 return this.currentFetchPromise; | 470 return this.currentFetchPromise; |
| 460 } | 471 } |
| 461 this.clearCurrentOperation(); | 472 this.clearCurrentOperation(); |
| 462 | 473 |
| 463 // If our provider is finished, then do nothing. | 474 // If our provider is finished, then do nothing. |
| 464 if (this.fetchedFullStream) { | 475 if (this.fetchedFullStream) { |
| 465 // There are no more logs. | 476 // There are no more logs. |
| 477 this.updateControls(); |
| 466 return undefined; | 478 return undefined; |
| 467 } | 479 } |
| 468 | 480 |
| 469 // If we're asked to fetch BOTTOM, but we're not split, fetch HEAD | 481 // If we're asked to fetch BOTTOM, but we're not split, fetch HEAD |
| 470 // instead. | 482 // instead. |
| 471 if (l === Location.BOTTOM && !this.isSplit) { | 483 if (l === Location.BOTTOM && !this.isSplit) { |
| 472 l = Location.HEAD; | 484 l = Location.HEAD; |
| 473 } | 485 } |
| 474 | 486 |
| 475 // Rotate our fetch ID. This will effectively cancel any pending fetches. | 487 // Rotate our fetch ID. This will effectively cancel any pending fetches. |
| (...skipping 19 matching lines...) Expand all Loading... |
| 495 hasLogs = await this.fetchLocationRound(l, op); | 507 hasLogs = await this.fetchLocationRound(l, op); |
| 496 } catch (err) { | 508 } catch (err) { |
| 497 console.log('Fetch failed with error:', err); | 509 console.log('Fetch failed with error:', err); |
| 498 | 510 |
| 499 // Cancel the timer here, since we may enter other states in this | 511 // Cancel the timer here, since we may enter other states in this |
| 500 // "catch" block and we don't want to have the timer override them. | 512 // "catch" block and we don't want to have the timer override them. |
| 501 loadingWhileTimer.cancel(); | 513 loadingWhileTimer.cancel(); |
| 502 | 514 |
| 503 // If we've been canceled, discard this result. | 515 // If we've been canceled, discard this result. |
| 504 if (err === luci.Operation.CANCELLED) { | 516 if (err === luci.Operation.CANCELLED) { |
| 517 this.setIdleLoadingState(); |
| 505 return; | 518 return; |
| 506 } | 519 } |
| 507 | 520 |
| 508 if (err === NOT_AUTHENTICATED) { | 521 if (err === NOT_AUTHENTICATED) { |
| 509 this.loadingState = LoadingState.NEEDS_AUTH; | 522 this.loadingState = LoadingState.NEEDS_AUTH; |
| 510 | 523 |
| 511 // We failed because we were not authenticated. Mark this | 524 // We failed because we were not authenticated. Mark this |
| 512 // so we can retry if that state changes. | 525 // so we can retry if that state changes. |
| 513 await this.authChangedPromise; | 526 await this.authChangedPromise; |
| 514 | 527 |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 550 } | 563 } |
| 551 | 564 |
| 552 // Clear our loading state (updates controls automatically). | 565 // Clear our loading state (updates controls automatically). |
| 553 this.loadingState = LoadingState.RENDERING; | 566 this.loadingState = LoadingState.RENDERING; |
| 554 | 567 |
| 555 // Initiate the next render. This will happen in the | 568 // Initiate the next render. This will happen in the |
| 556 // background while we enqueue our next fetch. | 569 // background while we enqueue our next fetch. |
| 557 let doRender = async () => { | 570 let doRender = async () => { |
| 558 await this.renderLogs(buf, l); | 571 await this.renderLogs(buf, l); |
| 559 if (this.loadingState === LoadingState.RENDERING) { | 572 if (this.loadingState === LoadingState.RENDERING) { |
| 560 this.loadingState = LoadingState.NONE; | 573 this.setIdleLoadingState(); |
| 561 } | 574 } |
| 562 }; | 575 }; |
| 563 this.renderPromise = doRender(); | 576 this.renderPromise = doRender(); |
| 564 | 577 |
| 565 if (this.fetchedFullStream) { | 578 if (this.fetchedFullStream) { |
| 566 return false; | 579 return false; |
| 567 } | 580 } |
| 568 return hasLogs; | 581 return hasLogs; |
| 569 } | 582 } |
| 570 | 583 |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 621 /** | 634 /** |
| 622 * Sets whether the next fetch will pull from the tail (end) or the top | 635 * Sets whether the next fetch will pull from the tail (end) or the top |
| 623 * (begining) region. | 636 * (begining) region. |
| 624 * | 637 * |
| 625 * This is only relevant when there is a log split. | 638 * This is only relevant when there is a log split. |
| 626 */ | 639 */ |
| 627 setFetchFromTail(v: boolean) { | 640 setFetchFromTail(v: boolean) { |
| 628 this.fetchFromTail = v; | 641 this.fetchFromTail = v; |
| 629 } | 642 } |
| 630 | 643 |
| 631 /** | |
| 632 * Sets whether automatic loading is enabled. | |
| 633 * | |
| 634 * When enabled, a new fetch will immediately be dispatched when a previous | |
| 635 * fetch finishes so long as there is still stream data to load. | |
| 636 */ | |
| 637 setAutomatic(v: boolean) { | |
| 638 this.automatic = v; | |
| 639 if (v) { | |
| 640 // Passively kick off a new fetch. | |
| 641 this.fetch(false); | |
| 642 } | |
| 643 } | |
| 644 | |
| 645 private buildStreamStatus(v: LogStreamStatus[]): StreamStatusEntry[] { | 644 private buildStreamStatus(v: LogStreamStatus[]): StreamStatusEntry[] { |
| 646 let maxStatus = LogDog.FetchStatus.IDLE; | 645 let maxStatus = LogDog.FetchStatus.IDLE; |
| 647 let maxStatusCount = 0; | 646 let maxStatusCount = 0; |
| 648 let needsAuth = false; | 647 let needsAuth = false; |
| 649 | 648 |
| 650 // Prune any finished entries and accumulate them for status bar change. | 649 // Prune any finished entries and accumulate them for status bar change. |
| 651 v = (v || []).filter((st) => { | 650 v = (v || []).filter((st) => { |
| 652 needsAuth = (needsAuth || st.needsAuth); | 651 needsAuth = (needsAuth || st.needsAuth); |
| 653 | 652 |
| 654 if (st.fetchStatus > maxStatus) { | 653 if (st.fetchStatus > maxStatus) { |
| (...skipping 789 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1444 | 1443 |
| 1445 // Get the next log and increment our index. | 1444 // Get the next log and increment our index. |
| 1446 let log = this.logs[this.index++]; | 1445 let log = this.logs[this.index++]; |
| 1447 if (this.index >= this.logs.length) { | 1446 if (this.index >= this.logs.length) { |
| 1448 this.logs = null; | 1447 this.logs = null; |
| 1449 } | 1448 } |
| 1450 return log; | 1449 return log; |
| 1451 } | 1450 } |
| 1452 } | 1451 } |
| 1453 } | 1452 } |
| OLD | NEW |