From c01727b0ad396e0ffc471a3b5574455c78d0b1d2 Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Wed, 5 Jul 2023 18:32:43 +0200 Subject: [PATCH] Avoid API queries for some events These events change the state in a predictable way. Avoid the expensive updateContainer() call for these, to avoid multiple calls overlapping each other. See https://github.com/containers/podman/issues/19124 The exact `StartedAt` value for the "start" event is unfortunately not part of the event data, but we can approximate it very well with the event time stamp. This doesn't have to be 100% correct: The only place where we use that is the restart detection in testLifecycleOperations. Adjust that to not require the data-started-at to be identical to the CLI output, just that it changes. --- src/app.jsx | 54 +++++++++++++++++++++++++++++++++++++----- test/check-application | 6 ++--- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/app.jsx b/src/app.jsx index 13c3353e8..0863a7962 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -33,6 +33,7 @@ import ContainerHeader from './ContainerHeader.jsx'; import Containers from './Containers.jsx'; import Images from './Images.jsx'; import * as client from './client.js'; +import { debug } from './util.js'; const _ = cockpit.gettext; @@ -276,6 +277,27 @@ class Application extends React.Component { return new_wait; } + /* lightweight version of updateContainer, but still needs the same serialization */ + updateContainerState(id, system, props) { + const idx = id + system.toString(); + + if (!this.state.containers[idx]) + // we got an event for a container which we didn't introspect yet + return this.updateContainer(id, system); + + const wait = this.pendingUpdateContainer[idx] ?? Promise.resolve(); + + wait.then(() => this.setState(prevState => { + const containers = { ...prevState.containers }; + const newContainer = { ...containers[idx], State: { ...containers[idx].State, ...props } }; + containers[idx] = newContainer; + debug(system, "updateContainerState", idx, "to", JSON.stringify(newContainer.State)); + return { containers }; + })); + this.pendingUpdateContainer[idx] = wait; + wait.finally(() => { delete this.pendingUpdateContainer[idx] }); + } + updateImage(id, system) { client.getImages(system, id) .then(reply => { @@ -343,6 +365,15 @@ class Application extends React.Component { case 'unmount': case 'wait': break; + + /* The following events just change the State properties */ + case 'pause': + this.updateContainerState(id, system, { Status: "paused" }); + break; + case 'unpause': + this.updateContainerState(id, system, { Status: "running" }); + break; + /* The following events need only to update the Container list * We do get the container affected in the event object but for * now we 'll do a batch update @@ -353,17 +384,28 @@ class Application extends React.Component { (event.Actor.Attributes.podId ? this.updatePod(event.Actor.Attributes.podId, system) : this.updatePods(system) - ).then(() => this.updateContainer(id, system, event)); + ).then(() => this.updateContainerState( + id, system, + { + Status: "running", + StartedAt: new Date(event.timeNano / 1000000).toISOString() + })); break; - case 'checkpoint': + + case 'died': + case 'stop': + this.updateContainerState(id, system, { Status: "stopped" }); + break; + case 'cleanup': + // don't bother with setting FinishedAt, we don't use it anywhere + this.updateContainerState(id, system, { Status: "exited" }); + break; + + case 'checkpoint': case 'create': - case 'died': case 'exec_died': // HACK: pick up health check runs, see https://github.com/containers/podman/issues/19237 - case 'pause': case 'restore': - case 'stop': - case 'unpause': case 'rename': // rename event is available starting podman v4.1; until then the container does not get refreshed after renaming this.updateContainer(id, system, event); break; diff --git a/test/check-application b/test/check-application index 66f1737e3..f871bafff 100755 --- a/test/check-application +++ b/test/check-application @@ -1228,10 +1228,10 @@ class TestApplication(testlib.MachineCase): # Restart the container; there is no steady state change in the visible UI, so look for # a changed data-started-at attribute old_start = self.getStartTime("swamped-crate", auth=auth) - b.wait_in_text(f'#containers-containers tr[data-started-at="{old_start}"]', "swamped-crate") + old_start_dom = b.attr("#containers-containers tr[data-row-id", "data-started-at") self.performContainerAction(IMG_BUSYBOX, "Force restart") - new_start = self.waitRestart("swamped-crate", old_start, auth=auth) - b.wait_in_text(f'#containers-containers tr[data-started-at="{new_start}"]', "swamped-crate") + self.waitRestart("swamped-crate", old_start, auth=auth) + b.wait_not_attr("#containers-containers tr[data-row-id", "data-started-at", old_start_dom) self.waitContainer(container_sha, auth, name='swamped-crate', image=IMG_BUSYBOX, state='Running') self.waitContainerRow(IMG_BUSYBOX)