Merge lp:~jameinel/juju-core/gomaxprocs-apiserver-1290841 into lp:~go-bot/juju-core/trunk

Proposed by John A Meinel
Status: Merged
Approved by: John A Meinel
Approved revision: no longer in the source branch.
Merged at revision: 2454
Proposed branch: lp:~jameinel/juju-core/gomaxprocs-apiserver-1290841
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 231 lines (+144/-1)
6 files modified
cmd/jujud/machine.go (+15/-0)
cmd/jujud/machine_test.go (+42/-0)
cmd/jujud/main.go (+1/-1)
utils/export_test.go (+9/-0)
utils/gomaxprocs.go (+26/-0)
utils/gomaxprocs_test.go (+51/-0)
To merge this branch: bzr merge lp:~jameinel/juju-core/gomaxprocs-apiserver-1290841
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+211261@code.launchpad.net

Commit message

cmd/juju/machine: Enable GOMAXPROCS(NumCPU)

As part of my scale testing work, I noticed the API server is CPU bound
in many circumstances (logging in lots of machines at the same time).
This changes it so machine agents that manage the environment (i.e. the
API Server) changes the default for GOMAXPROCS to the number of CPUs.

Description of the change

cmd/juju/machine: Enable GOMAXPROCS(NumCPU)

As part of my scale testing work, I noticed the API server is CPU bound
in many circumstances (logging in lots of machines at the same time).
However, we know that our test suite doesn't play nicely if we have
GOMAXPROCS>1. (Something about it running multiple tests concurrently
and tests that mutate global state.)

To avoid this affecting lots of code, I did it as a two phase approach.
We only will set GOMAXPROCS if

  1) GOMAXPROCS isn't already set in the environment. Since that would
     mean the go runtime would have already done its thing.
  2) We are running in response to the jujuDMain() (so it won't trigger
     in normal test suite operations.)
  3) We are running JobManageEnviron, so this will only affect the API
     server, and not all the other machines. (Even though it should be
     fine, if we were CPU bound there, we probably don't want to
     saturate machines that are running real workloads.)

I added tests that things get called properly when they are (and
aren't) enabled, but I didn't add a test that jujuDMain enables it
properly. I couldn't see an obvious way to hook into that code, so I
filed bug #1293383. Though if someone has a good way to test it, I'll
just do so.

This also has a change to how the MachineAgent works, in that it now
has a channel to let us know once it has called all of the StartWorker
steps and is going into the Wait() step. It made it possible to test
that we *didn't* call UseMultipleCPUs() for agents that aren't the API
server.

https://codereview.appspot.com/76120044/

To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :

Reviewers: mp+211261_code.launchpad.net,

Message:
Please take a look.

Description:
cmd/juju/machine: Enable GOMAXPROCS(NumCPU)

As part of my scale testing work, I noticed the API server is CPU bound
in many circumstances (logging in lots of machines at the same time).
However, we know that our test suite doesn't play nicely if we have
GOMAXPROCS>1. (Something about it running multiple tests concurrently
and tests that mutate global state.)

To avoid this affecting lots of code, I did it as a two phase approach.
We only will set GOMAXPROCS if

   1) GOMAXPROCS isn't already set in the environment. Since that would
      mean the go runtime would have already done its thing.
   2) We are running in response to the jujuDMain() (so it won't trigger
      in normal test suite operations.)
   3) We are running JobManageEnviron, so this will only affect the API
      server, and not all the other machines. (Even though it should be
      fine, if we were CPU bound there, we probably don't want to
      saturate machines that are running real workloads.)

I added tests that things get called properly when they are (and
aren't) enabled, but I didn't add a test that jujuDMain enables it
properly. I couldn't see an obvious way to hook into that code, so I
filed bug #1293383. Though if someone has a good way to test it, I'll
just do so.

This also has a change to how the MachineAgent works, in that it now
has a channel to let us know once it has called all of the StartWorker
steps and is going into the Wait() step. It made it possible to test
that we *didn't* call UseMultipleCPUs() for agents that aren't the API
server.

https://code.launchpad.net/~jameinel/juju-core/gomaxprocs-apiserver-1290841/+merge/211261

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/76120044/

Affected files (+223, -1 lines):
   A [revision details]
   M cmd/jujud/machine.go
   M cmd/jujud/machine_test.go
   M cmd/jujud/main.go
   A utils/export_test.go
   A utils/gomaxprocs.go
   A utils/gomaxprocs_test.go

Revision history for this message
Roger Peppe (rogpeppe) wrote :

This does not LGTM - I think it could be considerably simpler - a
suggestion below.

https://codereview.appspot.com/76120044/diff/1/utils/gomaxprocs.go
File utils/gomaxprocs.go (right):

https://codereview.appspot.com/76120044/diff/1/utils/gomaxprocs.go#newcode12
utils/gomaxprocs.go:12: var mu = sync.Mutex{}
This does not fill me with joy.
If our tests really do not play nicely with GOMAXPROCS>1,
(they *really* should, but I understand that they
might not) there's a much easier way to override it - just set
the environment variable.

However, for the jujud tests specifically, the tests
pass fine for me when jujud sets GOMAXPROCS.

I suggest that we add just implement the following
function in cmd/jujud
(I don't think it's generally useful enough to deserve a place in utils,
but it can go here if you think it's worth it):

func UseMultipleCPUs() {
    if os.Getenv("GOMAXPROCS") == "" {
        runtime.GOMAXPROCS(runtime.NumCPU())
    }
}

and call it from the same place it's called currently.
To test it, you could declare it as a variable and
replace it to ensure it's actually called.

If we really find that it makes the tests flaky,
the relevant tests can patch $GOMAXPROCS.

https://codereview.appspot.com/76120044/

Revision history for this message
Dave Cheney (dave-cheney) wrote :

On Mon, Mar 17, 2014 at 9:03 PM, Roger Peppe <email address hidden>wrote:

> This does not LGTM - I think it could be considerably simpler - a
> suggestion below.
>

I second rogers' objections, mainly on emotional grounds.

>
>
> https://codereview.appspot.com/76120044/diff/1/utils/gomaxprocs.go
> File utils/gomaxprocs.go (right):
>
>
> https://codereview.appspot.com/76120044/diff/1/utils/gomaxprocs.go#newcode12
> utils/gomaxprocs.go:12: var mu = sync.Mutex{}
> This does not fill me with joy.
> If our tests really do not play nicely with GOMAXPROCS>1,
> (they *really* should, but I understand that they
> might not) there's a much easier way to override it - just set
> the environment variable.
>
> However, for the jujud tests specifically, the tests
> pass fine for me when jujud sets GOMAXPROCS.
>
> I suggest that we add just implement the following
> function in cmd/jujud
> (I don't think it's generally useful enough to deserve a place in utils,
> but it can go here if you think it's worth it):
>
> func UseMultipleCPUs() {
> if os.Getenv("GOMAXPROCS") == "" {
> runtime.GOMAXPROCS(runtime.NumCPU())
> }
> }
>
>
This shoudn't be needed, if GOMAXPROCS is set in the environment then the
runtime already has initialised the value of runtime.GOMAXPROCS to that
value.

What this appears to be doing is _defaulting_ to runtime.NumCPU(), _unless_
runtime.GOMAXPROCS is set to a specific value, one can only assume the
value "1".

Really, this is not the Go way. I'm willing to accept arguments that jujud
needs this to effectively handle a large number of PBKDF2 handshakes, but
if so this should be done in cloud init or somethign else that can set the
GOMAXPROCS environment variable inside the upstart script ... we do already
have that information in the ec2 tables.`

>
> and call it from the same place it's called currently.
> To test it, you could declare it as a variable and
> replace it to ensure it's actually called.
>
> If we really find that it makes the tests flaky,
> the relevant tests can patch $GOMAXPROCS.
>
> https://codereview.appspot.com/76120044/
>
> --
>
> https://code.launchpad.net/~jameinel/juju-core/gomaxprocs-apiserver-1290841/+merge/211261
> You are subscribed to branch lp:juju-core.
>

Revision history for this message
John A Meinel (jameinel) wrote :

On 2014/03/17 10:02:15, rog wrote:
> This does not LGTM - I think it could be considerably simpler - a
suggestion
> below.

> https://codereview.appspot.com/76120044/diff/1/utils/gomaxprocs.go
> File utils/gomaxprocs.go (right):

https://codereview.appspot.com/76120044/diff/1/utils/gomaxprocs.go#newcode12
> utils/gomaxprocs.go:12: var mu = sync.Mutex{}
> This does not fill me with joy.
> If our tests really do not play nicely with GOMAXPROCS>1,
> (they *really* should, but I understand that they
> might not) there's a much easier way to override it - just set
> the environment variable.

> However, for the jujud tests specifically, the tests
> pass fine for me when jujud sets GOMAXPROCS.

> I suggest that we add just implement the following
> function in cmd/jujud
> (I don't think it's generally useful enough to deserve a place in
utils,
> but it can go here if you think it's worth it):

> func UseMultipleCPUs() {
> if os.Getenv("GOMAXPROCS") == "" {
> runtime.GOMAXPROCS(runtime.NumCPU())
> }
> }

> and call it from the same place it's called currently.
> To test it, you could declare it as a variable and
> replace it to ensure it's actually called.

> If we really find that it makes the tests flaky,
> the relevant tests can patch $GOMAXPROCS.

The new code is simplified to just always call GOMAXPROCS without the
Enable step.

https://codereview.appspot.com/76120044/

Revision history for this message
John A Meinel (jameinel) wrote :

Please take a look.

https://codereview.appspot.com/76120044/diff/1/utils/gomaxprocs.go
File utils/gomaxprocs.go (right):

https://codereview.appspot.com/76120044/diff/1/utils/gomaxprocs.go#newcode12
utils/gomaxprocs.go:12: var mu = sync.Mutex{}
On 2014/03/17 10:02:16, rog wrote:
> This does not fill me with joy.
> If our tests really do not play nicely with GOMAXPROCS>1,
> (they *really* should, but I understand that they
> might not) there's a much easier way to override it - just set
> the environment variable.

> However, for the jujud tests specifically, the tests
> pass fine for me when jujud sets GOMAXPROCS.

> I suggest that we add just implement the following
> function in cmd/jujud
> (I don't think it's generally useful enough to deserve a place in
utils,
> but it can go here if you think it's worth it):

> func UseMultipleCPUs() {
> if os.Getenv("GOMAXPROCS") == "" {
> runtime.GOMAXPROCS(runtime.NumCPU())
> }
> }

> and call it from the same place it's called currently.
> To test it, you could declare it as a variable and
> replace it to ensure it's actually called.

> If we really find that it makes the tests flaky,
> the relevant tests can patch $GOMAXPROCS.

Done.

https://codereview.appspot.com/76120044/

Revision history for this message
John A Meinel (jameinel) wrote :
Revision history for this message
Roger Peppe (rogpeppe) wrote :
Download full text (3.6 KiB)

I like that much better, thanks.
Some further simplification suggestions below.

https://codereview.appspot.com/76120044/diff/20001/cmd/jujud/main.go
File cmd/jujud/main.go (right):

https://codereview.appspot.com/76120044/diff/20001/cmd/jujud/main.go#newcode1
cmd/jujud/main.go:1: // Copyright 2012-2014 Canonical Ltd.
I think it's reasonable to leave the copyright dates alone. That's
certainly the explicit convention in Go core, and Juju de facto.

https://codereview.appspot.com/76120044/diff/40001/cmd/jujud/machine.go
File cmd/jujud/machine.go (right):

https://codereview.appspot.com/76120044/diff/40001/cmd/jujud/machine.go#newcode75
cmd/jujud/machine.go:75: workersStarted chan struct{}
Perhaps some documentation for this might be appropriate, given
that it's only here for tests.

Or a method:

// WorkersStarted returns a channel that's closed when
// the top level workers have been started. This is
// provided for testing purposes.
func (a *MachineAgent) WorkersStarted() <-chan struct{}
     return a.workersStarted
}

https://codereview.appspot.com/76120044/diff/40001/cmd/jujud/machine.go#newcode345
cmd/jujud/machine.go:345: useMultipleCPUs()
If you took logging out of the utils package, you could add a log
message here instead:

    logger.Debugf("GOMAXPROCS set to %d", runtime.GOMAXPROCS(0))

That's probably sufficient for diagnosing gomaxprocs-related problems.

https://codereview.appspot.com/76120044/diff/40001/utils/export_test.go
File utils/export_test.go (right):

https://codereview.appspot.com/76120044/diff/40001/utils/export_test.go#newcode9
utils/export_test.go:9: func OverrideCPUFuncs(maxprocsfunc func(int)
int, numCPUFunc func() int) func() {
Assuming we go with this, I'd do:

var (
     GoMaxProcs = &gomaxprocs
     NumCPU = &numcpu
)

then we can use PatchValue in the tests.

https://codereview.appspot.com/76120044/diff/40001/utils/gomaxprocs.go
File utils/gomaxprocs.go (right):

https://codereview.appspot.com/76120044/diff/40001/utils/gomaxprocs.go#newcode11
utils/gomaxprocs.go:11: var gomaxprocs = runtime.GOMAXPROCS
This seems a bit of overkill for such a function with such simple logic.
I'd just test that it does the right thing (even though it's not a full
test on a machine with a single cpu). It'll fail on any machine
with more than one CPU, which seems adequate to me. Is that overly
pragmatic?

https://codereview.appspot.com/76120044/diff/40001/utils/gomaxprocs.go#newcode16
utils/gomaxprocs.go:16: // variable if it is set.
// UseMultipleCPUs sets GOMAXPROCS to the number of CPU
// cores unless overridden by the GOMAXPROCS environment
// variable.

?

https://codereview.appspot.com/76120044/diff/40001/utils/gomaxprocs.go#newcode20
utils/gomaxprocs.go:20: logger.Debugf("GOMAXPROCS already set in
environment to %q, %d internally",
I'm not that keen on utils functions producing log messages (and since
they're only at Debug level, they can't really be that important).
Honestly,
I'd just do:

func UseMultipleCPUs() {
      if os.Getenv("GOMAXPROCS") == "" {
          runtime.GOMAXPROCS(runtime.NumCPU())
      }
}

and be done with it. Alternatively, if it lived in cmd/jujud then
logging might be more appropriate, as it can ...

Read more...

Revision history for this message
John A Meinel (jameinel) wrote :
Download full text (4.0 KiB)

Please take a look.

https://codereview.appspot.com/76120044/diff/40001/cmd/jujud/machine.go
File cmd/jujud/machine.go (right):

https://codereview.appspot.com/76120044/diff/40001/cmd/jujud/machine.go#newcode75
cmd/jujud/machine.go:75: workersStarted chan struct{}
On 2014/03/19 10:50:28, rog wrote:
> Perhaps some documentation for this might be appropriate, given
> that it's only here for tests.

> Or a method:

> // WorkersStarted returns a channel that's closed when
> // the top level workers have been started. This is
> // provided for testing purposes.
> func (a *MachineAgent) WorkersStarted() <-chan struct{}
> return a.workersStarted
> }

Done.

https://codereview.appspot.com/76120044/diff/40001/cmd/jujud/machine.go#newcode345
cmd/jujud/machine.go:345: useMultipleCPUs()
On 2014/03/19 10:50:28, rog wrote:
> If you took logging out of the utils package, you could add a log
> message here instead:

> logger.Debugf("GOMAXPROCS set to %d", runtime.GOMAXPROCS(0))

> That's probably sufficient for diagnosing gomaxprocs-related problems.

That explicitly misses out on distinguishing between GOMAXPROCS was set
in the environment vs GOMAXPROCS was set from NumCPU(). Which (IMO) is
actually a useful thing to distinguish.

https://codereview.appspot.com/76120044/diff/40001/utils/gomaxprocs.go
File utils/gomaxprocs.go (right):

https://codereview.appspot.com/76120044/diff/40001/utils/gomaxprocs.go#newcode11
utils/gomaxprocs.go:11: var gomaxprocs = runtime.GOMAXPROCS
On 2014/03/19 10:50:28, rog wrote:
> This seems a bit of overkill for such a function with such simple
logic.
> I'd just test that it does the right thing (even though it's not a
full
> test on a machine with a single cpu). It'll fail on any machine
> with more than one CPU, which seems adequate to me. Is that overly
> pragmatic?

Unless someone (we?) start setting GOMAXPROCS in the environment for
regular running of tests, etc.

I find it particularly useful to be able to write tests that are
abstracted from the runtime.

https://codereview.appspot.com/76120044/diff/40001/utils/gomaxprocs.go#newcode16
utils/gomaxprocs.go:16: // variable if it is set.
On 2014/03/19 10:50:28, rog wrote:
> // UseMultipleCPUs sets GOMAXPROCS to the number of CPU
> // cores unless overridden by the GOMAXPROCS environment
> // variable.

> ?

Done.

https://codereview.appspot.com/76120044/diff/40001/utils/gomaxprocs.go#newcode20
utils/gomaxprocs.go:20: logger.Debugf("GOMAXPROCS already set in
environment to %q, %d internally",
On 2014/03/19 10:50:28, rog wrote:
> I'm not that keen on utils functions producing log messages (and since
they're
> only at Debug level, they can't really be that important). Honestly,
> I'd just do:

> func UseMultipleCPUs() {
> if os.Getenv("GOMAXPROCS") == "" {
> runtime.GOMAXPROCS(runtime.NumCPU())
> }
> }

> and be done with it. Alternatively, if it lived in cmd/jujud then
logging might
> be more appropriate, as it can be more location-specific there.

We already have logging in utils (I didn't have to create 'logger'), so
I'm pretty sure its fine as a pattern.

I do think it is useful to be logged, and I think any code that does
start changing G...

Read more...

Revision history for this message
Roger Peppe (rogpeppe) wrote :
Download full text (3.5 KiB)

LGTM, your call.

https://codereview.appspot.com/76120044/diff/40001/cmd/jujud/machine.go
File cmd/jujud/machine.go (right):

https://codereview.appspot.com/76120044/diff/40001/cmd/jujud/machine.go#newcode345
cmd/jujud/machine.go:345: useMultipleCPUs()
On 2014/03/20 06:48:46, jameinel wrote:
> On 2014/03/19 10:50:28, rog wrote:
> > If you took logging out of the utils package, you could add a log
> > message here instead:
> >
> > logger.Debugf("GOMAXPROCS set to %d", runtime.GOMAXPROCS(0))
> >
> > That's probably sufficient for diagnosing gomaxprocs-related
problems.

> That explicitly misses out on distinguishing between GOMAXPROCS was
set in the
> environment vs GOMAXPROCS was set from NumCPU(). Which (IMO) is
actually a
> useful thing to distinguish.

I'm struggling to think of a scenario where we'd really care, but fair
enough.

https://codereview.appspot.com/76120044/diff/40001/utils/gomaxprocs.go
File utils/gomaxprocs.go (right):

https://codereview.appspot.com/76120044/diff/40001/utils/gomaxprocs.go#newcode11
utils/gomaxprocs.go:11: var gomaxprocs = runtime.GOMAXPROCS
> Unless someone (we?) start setting GOMAXPROCS in the environment for
> regular running of tests, etc.

It's trivial to patch the GOMAXPROCS environment variable in the test.

> I find it particularly useful to be able to write tests that are
> abstracted from the runtime.

Fair enough. I like seeing code that's as straightforwardly obvious as
possible, but I appreciate that it's a trade-off.

https://codereview.appspot.com/76120044/diff/40001/utils/gomaxprocs.go#newcode20
utils/gomaxprocs.go:20: logger.Debugf("GOMAXPROCS already set in
environment to %q, %d internally",
On 2014/03/20 06:48:46, jameinel wrote:
> On 2014/03/19 10:50:28, rog wrote:
> > I'm not that keen on utils functions producing log messages (and
since they're
> > only at Debug level, they can't really be that important). Honestly,
> > I'd just do:
> >
> > func UseMultipleCPUs() {
> > if os.Getenv("GOMAXPROCS") == "" {
> > runtime.GOMAXPROCS(runtime.NumCPU())
> > }
> > }
> >
> > and be done with it. Alternatively, if it lived in cmd/jujud then
logging
> might
> > be more appropriate, as it can be more location-specific there.

> We already have logging in utils (I didn't have to create 'logger'),
so I'm
> pretty sure its fine as a pattern.

> I do think it is useful to be logged, and I think any code that does
start
> changing GOMAXPROCS at runtime should be logging that fact. So I think
it is
> perfectly appropriate to log here.

There is only one other place in utils that logs messages, and that
place (GetAddressForInterface) is a mistake (it should return an
annotated
error rather than logging).

> Given we stopped doing the Enable vs set it, and set it always, we
could have
> just embedded it, but if we do grow more places that want to take
advantage of
> >1 CPU, I'd rather have that logic centralized.

FWIW, no non-main package should be changing GOMAXPROCS at all,
which is why IMHO it's a mistake to put it in utils at all. It might fit
reasonably inside cmd, I guess.

https://codereview.appspot.com/76120044/diff/60001/utils/gomaxprocs.go
File utils/gomaxprocs.go (right):

ht...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cmd/jujud/machine.go'
2--- cmd/jujud/machine.go 2014-03-18 09:21:53 +0000
3+++ cmd/jujud/machine.go 2014-03-20 09:04:40 +0000
4@@ -28,6 +28,7 @@
5 "launchpad.net/juju-core/state/apiserver"
6 "launchpad.net/juju-core/upgrades"
7 "launchpad.net/juju-core/upstart"
8+ "launchpad.net/juju-core/utils"
9 "launchpad.net/juju-core/version"
10 "launchpad.net/juju-core/worker"
11 "launchpad.net/juju-core/worker/authenticationworker"
12@@ -60,6 +61,8 @@
13
14 var jujuRun = "/usr/local/bin/juju-run"
15
16+var useMultipleCPUs = utils.UseMultipleCPUs
17+
18 // MachineAgent is a cmd.Command responsible for running a machine agent.
19 type MachineAgent struct {
20 cmd.CommandBase
21@@ -69,6 +72,7 @@
22 runner worker.Runner
23 upgradeComplete chan struct{}
24 stateOpened chan struct{}
25+ workersStarted chan struct{}
26 st *state.State
27 }
28
29@@ -96,6 +100,7 @@
30 a.runner = newRunner(isFatal, moreImportant)
31 a.upgradeComplete = make(chan struct{})
32 a.stateOpened = make(chan struct{})
33+ a.workersStarted = make(chan struct{})
34 return nil
35 }
36
37@@ -155,6 +160,8 @@
38 a.runner.StartWorker("termination", func() (worker.Worker, error) {
39 return terminationworker.NewWorker(), nil
40 })
41+ // At this point, all workers will have been configured to start
42+ close(a.workersStarted)
43 err := a.runner.Wait()
44 if err == worker.ErrTerminateAgent {
45 err = a.uninstallAgent()
46@@ -335,6 +342,7 @@
47 case state.JobHostUnits:
48 // Implemented in APIWorker.
49 case state.JobManageEnviron:
50+ useMultipleCPUs()
51 a.startWorkerAfterUpgrade(runner, "instancepoller", func() (worker.Worker, error) {
52 return instancepoller.NewWorker(st), nil
53 })
54@@ -468,6 +476,13 @@
55 return a.Conf.config.WriteUpgradedToVersion(version.Current.Number)
56 }
57
58+// WorkersStarted returns a channel that's closed once all top level workers
59+// have been started. This is provided for testing purposes.
60+func (a *MachineAgent) WorkersStarted() <-chan struct{} {
61+ return a.workersStarted
62+
63+}
64+
65 func (a *MachineAgent) Entity(st *state.State) (AgentState, error) {
66 m, err := st.Machine(a.MachineId)
67 if err != nil {
68
69=== modified file 'cmd/jujud/machine_test.go'
70--- cmd/jujud/machine_test.go 2014-03-19 21:23:05 +0000
71+++ cmd/jujud/machine_test.go 2014-03-20 09:04:40 +0000
72@@ -397,6 +397,48 @@
73
74 }
75
76+func (s *MachineSuite) TestManageEnvironCallsUseMultipleCPUs(c *gc.C) {
77+ // If it has been enabled, the JobManageEnviron agent should call utils.UseMultipleCPUs
78+ usefulVersion := version.Current
79+ usefulVersion.Series = "quantal"
80+ envtesting.AssertUploadFakeToolsVersions(c, s.Conn.Environ.Storage(), usefulVersion)
81+ m, _, _ := s.primeAgent(c, version.Current, state.JobManageEnviron)
82+ s.setFakeMachineAddresses(c, m)
83+ calledChan := make(chan struct{}, 1)
84+ s.PatchValue(&useMultipleCPUs, func() { calledChan <- struct{}{} })
85+ // Now, start the agent, and observe that a JobManageEnviron agent
86+ // calls UseMultipleCPUs
87+ a := s.newAgent(c, m)
88+ defer a.Stop()
89+ go func() {
90+ c.Check(a.Run(nil), gc.IsNil)
91+ }()
92+ // Wait for configuration to be finished
93+ <-a.WorkersStarted()
94+ select {
95+ case <-calledChan:
96+ case <-time.After(coretesting.LongWait):
97+ c.Errorf("we failed to call UseMultipleCPUs()")
98+ }
99+ c.Check(a.Stop(), gc.IsNil)
100+ // However, an agent that just JobHostUnits doesn't call UseMultipleCPUs
101+ m2, _, _ := s.primeAgent(c, version.Current, state.JobHostUnits)
102+ s.setFakeMachineAddresses(c, m2)
103+ a2 := s.newAgent(c, m2)
104+ defer a2.Stop()
105+ go func() {
106+ c.Check(a2.Run(nil), gc.IsNil)
107+ }()
108+ // Wait until all the workers have been started, and then kill everything
109+ <-a2.workersStarted
110+ c.Check(a2.Stop(), gc.IsNil)
111+ select {
112+ case <-calledChan:
113+ c.Errorf("we should not have called UseMultipleCPUs()")
114+ case <-time.After(coretesting.ShortWait):
115+ }
116+}
117+
118 func (s *MachineSuite) waitProvisioned(c *gc.C, unit *state.Unit) (*state.Machine, instance.Id) {
119 c.Logf("waiting for unit %q to be provisioned", unit)
120 machineId, err := unit.AssignedMachineId()
121
122=== modified file 'cmd/jujud/main.go'
123--- cmd/jujud/main.go 2014-03-05 19:41:34 +0000
124+++ cmd/jujud/main.go 2014-03-20 09:04:40 +0000
125@@ -1,4 +1,4 @@
126-// Copyright 2012, 2013 Canonical Ltd.
127+// Copyright 2012-2014 Canonical Ltd.
128 // Licensed under the AGPLv3, see LICENCE file for details.
129
130 package main
131
132=== added file 'utils/export_test.go'
133--- utils/export_test.go 1970-01-01 00:00:00 +0000
134+++ utils/export_test.go 2014-03-20 09:04:40 +0000
135@@ -0,0 +1,9 @@
136+// Copyright 2013 Canonical Ltd.
137+// Licensed under the AGPLv3, see LICENCE file for details.
138+
139+package utils
140+
141+var (
142+ GOMAXPROCS = &gomaxprocs
143+ NumCPU = &numCPU
144+)
145
146=== added file 'utils/gomaxprocs.go'
147--- utils/gomaxprocs.go 1970-01-01 00:00:00 +0000
148+++ utils/gomaxprocs.go 2014-03-20 09:04:40 +0000
149@@ -0,0 +1,26 @@
150+// Copyright 2014 Canonical Ltd.
151+// Licensed under the AGPLv3, see LICENCE file for details.
152+
153+package utils
154+
155+import (
156+ "os"
157+ "runtime"
158+)
159+
160+var gomaxprocs = runtime.GOMAXPROCS
161+var numCPU = runtime.NumCPU
162+
163+// UseMultipleCPUs sets GOMAXPROCS to the number of CPU cores unless it has
164+// already been overridden by the GOMAXPROCS environment variable.
165+func UseMultipleCPUs() {
166+ if envGOMAXPROCS := os.Getenv("GOMAXPROCS"); envGOMAXPROCS != "" {
167+ n := gomaxprocs(0)
168+ logger.Debugf("GOMAXPROCS already set in environment to %q, %d internally",
169+ envGOMAXPROCS, n)
170+ return
171+ }
172+ n := numCPU()
173+ logger.Debugf("setting GOMAXPROCS to %d", n)
174+ gomaxprocs(n)
175+}
176
177=== added file 'utils/gomaxprocs_test.go'
178--- utils/gomaxprocs_test.go 1970-01-01 00:00:00 +0000
179+++ utils/gomaxprocs_test.go 2014-03-20 09:04:40 +0000
180@@ -0,0 +1,51 @@
181+// Copyright 2014 Canonical Ltd.
182+// Licensed under the AGPLv3, see LICENCE file for details.
183+
184+package utils_test
185+
186+import (
187+ "os"
188+
189+ gc "launchpad.net/gocheck"
190+
191+ "launchpad.net/juju-core/testing/testbase"
192+ "launchpad.net/juju-core/utils"
193+)
194+
195+type gomaxprocsSuite struct {
196+ testbase.LoggingSuite
197+ setmaxprocs chan int
198+ numCPUResponse int
199+ setMaxProcs int
200+}
201+
202+var _ = gc.Suite(&gomaxprocsSuite{})
203+
204+func (s *gomaxprocsSuite) SetUpTest(c *gc.C) {
205+ s.LoggingSuite.SetUpTest(c)
206+ // always stub out GOMAXPROCS so we don't actually change anything
207+ s.numCPUResponse = 2
208+ s.setMaxProcs = -1
209+ maxProcsFunc := func(n int) int {
210+ s.setMaxProcs = n
211+ return 1
212+ }
213+ numCPUFunc := func() int { return s.numCPUResponse }
214+ s.PatchValue(utils.GOMAXPROCS, maxProcsFunc)
215+ s.PatchValue(utils.NumCPU, numCPUFunc)
216+ s.PatchEnvironment("GOMAXPROCS", "")
217+}
218+
219+func (s *gomaxprocsSuite) TestUseMultipleCPUsDoesNothingWhenGOMAXPROCSSet(c *gc.C) {
220+ os.Setenv("GOMAXPROCS", "1")
221+ utils.UseMultipleCPUs()
222+ c.Check(s.setMaxProcs, gc.Equals, 0)
223+}
224+
225+func (s *gomaxprocsSuite) TestUseMultipleCPUsWhenEnabled(c *gc.C) {
226+ utils.UseMultipleCPUs()
227+ c.Check(s.setMaxProcs, gc.Equals, 2)
228+ s.numCPUResponse = 4
229+ utils.UseMultipleCPUs()
230+ c.Check(s.setMaxProcs, gc.Equals, 4)
231+}

Subscribers

People subscribed via source and target branches

to status/vote changes: