Offer a means to send the tty to the Apple log system#196
Offer a means to send the tty to the Apple log system#196akimd wants to merge 12 commits intomoby:masterfrom
Conversation
f7f7584 to
888ac48
Compare
|
Couple of generic comments:
|
Signed-off-by: Akim Demaille <akim.demaille@docker.com>
In some earlier experiment this function was called several times. Since it made the code slightly easier to understand, I left it. Signed-off-by: Akim Demaille <akim.demaille@docker.com>
Hyperkit.check verifies that if `h.Console == ConsoleStdio`, then either `isTerminal(os.Stdout)` or `h.StateDir != ""`. In the next commit, the device configuration becomes more complex, and simplifying this conditional by eliminating impossible situations makes this next commit clearer. Signed-off-by: Akim Demaille <akim.demaille@docker.com>
7179ee9 to
cba5521
Compare
50794bf to
d72b731
Compare
|
Hi @rn. Thanks for the tips! I have reached a point where this is really nice to use from Docker for Mac. |
src/lib/asl.c
Outdated
| buf = malloc(buf_capacity); | ||
| } else { | ||
| buf_capacity *= 2; | ||
| buf = realloc(buf, buf_capacity); |
There was a problem hiding this comment.
Both malloc and realloc can fail so you need to check here. Take extra care with realloc not to leak the original buffer -- although given there isn't much error handling you can do without a lot of reworking perhaps an xhyve_abort would be a reasonable choice and then leaks are somewhat immaterial.
You could also simplify using the fact that realloc(NULL, N) is equivalent to malloc(N).
There was a problem hiding this comment.
I did not check that allocation succeeded, because there are many places where this is not done, starting with that very file
Line 650 in b2a7a6a
What would you recommend? perror + exit?
src/lib/uart_emul.c
Outdated
|
|
||
| struct ttyfd tty; | ||
| struct log log; | ||
| bool asl; /* Output to Apple logger. */ |
There was a problem hiding this comment.
GH's rendering suggests a tabs vs spaces issue here
| } else if (strcmp("asl", backend) == 0) { | ||
| sc->asl = true; | ||
| asl_init(); | ||
| retval = 0; |
go/hyperkit.go
Outdated
| } | ||
|
|
||
| // openTty opens the tty files for reading, and returns it. | ||
| func (h *HyperKit) openTty() *os.File { |
There was a problem hiding this comment.
TTY is an abbreviation so either openTTY or opentty throughout would be preferable.
Signed-off-by: Akim Demaille <akim.demaille@docker.com>
|
I uploaded a version that should address your concerns. Thanks! |
src/lib/asl.c
Outdated
| /* Grow buf/buf_capacity. */ | ||
| static void buf_grow(void) | ||
| { | ||
| buf_capacity = buf_capacity ? 2 * buf_capacity : 1024; |
There was a problem hiding this comment.
Why not initialise the buffer capacity to 1024 in asl_init() and then just grow from there? Also, we should probably add an upper bound unconditionally flush if it is reached. Or more generally, is there a reason you just don't allocate a fixed buffer of, say 4k and flush it when it's full (or on \n). Seem's much simpler.
There was a problem hiding this comment.
I hesitated, but the code was so simple to make it dynamic, that I chose the safest path.
src/lib/asl.c
Outdated
| static aslmsg log_msg = NULL; | ||
|
|
||
| static unsigned char *buf = NULL; | ||
| static size_t buf_size = 0; |
There was a problem hiding this comment.
buf_size seems misnamed, as it is not the size of the buffer, but rather the index into the buffer where to write.
There was a problem hiding this comment.
That would depend on what you call size. Size is the size of the contents of the buffer (0 when it's empty). Capacity is the "available size".
There was a problem hiding this comment.
In pretty much all the code I know which handles buffers in C, size/sz etc refers to the size of a buffer (not the size of the contents). IMHO, something like buf_idx is much clearer
| ttywrite(&sc->tty, value); | ||
| if (sc->log.ring) | ||
| ringwrite(&sc->log, value); | ||
| if (sc->asl) |
There was a problem hiding this comment.
below it looks like the asl and log are exclusive, as a else if is used while here it seems as if they can be used together.
There was a problem hiding this comment.
I don't see that they are exclusive. What do you mean?
There was a problem hiding this comment.
sorry, I misread the snippet of code from below. This is fine.
src/lib/asl.c
Outdated
| if (!buf) { | ||
| perror("buf_grow"); | ||
| exit(1); | ||
| } |
There was a problem hiding this comment.
I'd have used xhyve_abort but this is ok too I guess.
There was a problem hiding this comment.
There were several exit(1) already. I don't know which coding style to follow, there are too many.
There was a problem hiding this comment.
I think it depends on whether the code was originally kernel, library or application code in the FreeBSD bhyve stack. Looks like xhyve_abort is only in "kernel" code so that was a bad suggestion, sorry. In src/lib abort and exit are basically even so lets stick with exit.
src/lib/asl.c
Outdated
| if (buf_size + 1 >= buf_capacity) { | ||
| buf_grow(); | ||
| } | ||
| if (c == '\n') { |
There was a problem hiding this comment.
I'm slightly concerned about what happens if the console is e.g. running some sort of curses app or something where \n might conceivably be quite rare, it could end up building up quite a large buffer.
Perhaps add an upper bound on the buffer and flush then even if no \n is observed?
Also don't you need to handle \0 specially here? Without such handling the buffer passed to asl_log will be truncated at any NUL in the stream. Perhaps just treat \0 as equivalent to \n?
There was a problem hiding this comment.
I have no idea what can be sent to tty here, whatever you suggest, I'll buy! I'm fine with your \0 = \n approach, or \0 = \\0 to make it visible, etc.
If you are aware of a tty2text approach, I'll take it too.
There was a problem hiding this comment.
I think what can be sent to the console here is effectively arbitrary bytes, could be anything, from raw binary to utf-16 encoded text, there's probably no good universal answer. Most frequently it'll be ASCII or UTF-8 though plus some escape codes, I expect.
How about:
- Put an upper limit of 4K on the buffer. If we reach this limit then flush unconditionally (could also make it a static 4K buffer if you like since the dynamism is no longer that important with a static limit in place).
- Treat
\nand\0the same: do not inject them in the log stream, instead flush the current buffer. - For
\insert it as\\(i.e. escape it, to distinguish from the result of the next rule) - If
!isprint(c)then escape it as hex\xx - otherwise simply insert
cas is.
This should preserve all of the useful text in the common case without injecting control characters or anything too weird into ASL (which I don't expect to handle anything especially crazy). It's not quite reversible since we've lost the distinction between \n and \0 but I think it'll do, including the \n would make things pretty unreadable in the common case.
The escaping makes the buffer size check a little more complicated, since it's no longer 1:1. I suggest just growing when you reach capacity - 3 rather than being too fancy.
There was a problem hiding this comment.
If we put an upper limit of 4k on the buffer, why not just statically allocate it?
There was a problem hiding this comment.
I don't like the escaping of !isprint. In my pet project I did exactly that, and as a result, every single UTF-8 non-ascii character was broken. I think we should not try to be too smart here.
There was a problem hiding this comment.
Looking at http://www.manpagez.com/man/3/asl_log/ I'm half wondering if perhaps ASL is already setup to deal with such things correctly. There's a bunch of ASL_ENCODING_* which look to only be used with asl_add_output_file but there seems to be a general implication that the internal DB treats things as NUL terminated strings ("All keys and values are NUL-character terminated C language strings. UTF-8 encoding may be used for non-ASCII characters.") and it understands escaping in the presentation layer to some extent.
So, while I'm a bit wary that we're going to bring things crashing down around us lets try just flushing on a) 4K limit and b) on \n or \0 and hope for the best.
On Docker for Windows, all the logs are aggregated: GUI, LinuxKit, etc. Currently debugging on Docker for Mac is less easy, in particular because LinuxKit's log are sent to the consolefile rings of fixed sized buffers. As a result, it's hard to find the interleaving of actions between Docker for Mac and LinuxKit, the console files are fragmented and full of '\0', etc. It is possible to have hyperkit redirect the tty's output to its stderr/stdout, and then its caller (Docker for Mac's driver) could redirect these streams to the logger. Unfortunately on recent macOS releases, asl is replaced by os_log which uses exclusively the path of the executable as source (changing `argv[0]` is useless). As a result, hyperkit's logs are "credited" to the driver. The most elegant approach is to embrace asl/os_log. Currently Docker for Mac still targets 10.11, and os_log appeared in macOS 10.12, so we cannot use directly os_log's API, we use it through the asl shim. Add a new device configuration, asl, that sends the tty's output to asl. Signed-off-by: Akim Demaille <akim.demaille@docker.com>
Signed-off-by: Akim Demaille <akim.demaille@docker.com>
Suggested by Rolf Neugebauer. Signed-off-by: Akim Demaille <akim.demaille@docker.com>
Suggested by Ian Campbell. Signed-off-by: Akim Demaille <akim.demaille@docker.com>
Suggested by Rolf Neugebauer. Signed-off-by: Akim Demaille <akim.demaille@docker.com>
src/lib/asl.c
Outdated
| asl_log(asl, log_msg, ASL_LEVEL_NOTICE, "%s", buf); | ||
| buf_idx = 0; | ||
| } else { | ||
| if (buf_idx + 2 >= sizeof buf) { |
There was a problem hiding this comment.
Please use sizeof(buf). If it were me I would also prefer to write buf_idx + 1 > sizeof(buf) but each to his own.
There was a problem hiding this comment.
I also prefer strict comparisons. And as a matter of fact, I also prefer stick to < and <= only, and never > or >= (lesson taught from Paul Eggert, a mentor of mine). But many people object to this style, and I wish to go forward, not open new debates.
Here, what I mean is if (! (buf_idx + 2 < sizeof buf)), so >= expresses more naturally the constraint.
Suggested by Ian Campbell.
rn
left a comment
There was a problem hiding this comment.
After the code looks ok, the commit need to be re-arranged. There should be:
- One commit adding ASL support to hyperkit
- One commit for whitespace clean up
- One commit to fix the link
- One commit for the changes to the go bindings
src/lib/asl.c
Outdated
| buf_idx = 0; | ||
| } else { | ||
| if (buf_idx + 2 >= sizeof(buf)) { | ||
| /* Running out of space, flush. */ |
There was a problem hiding this comment.
How does this flush the buffer? I would also expect this to reset the buf_idx. Actually the size check should be in the first conditional, something like:
if \n or \0 or if buffer is full, flush it otherwise add character to buffer.
There was a problem hiding this comment.
Well, asl_put('\n') flushes. Where should I start to help us see it?
There was a problem hiding this comment.
but then you start writing over the end of the buffer...
There was a problem hiding this comment.
It took me a moment to grok that too. How about adding asl_flush() with the contents of the \n case and calling that from both?
There was a problem hiding this comment.
@rn I think buf_idx + 2 >= sizeof(buf) ensures there is enough space for the \0 which would be added for flushing. I'm not quite sure why it is +2 rather than +1 though, with the use of >= it seems like it would flush a byte too soon (but, meh).
There was a problem hiding this comment.
@ijc just pointed out the "subtle" recursive call to asl_put(). This is just plain crazy! don't do that.
There was a problem hiding this comment.
I suppose intruding on the asl_* namespace also used by the OS provided functions is moderately confusing. Perhaps our wrappers should all be xhyve_asl_* to avoid that?
Suggested by Ian Campbell. Signed-off-by: Akim Demaille <akim.demaille@docker.com>
Suggested by Ian Campbell. Signed-off-by: Akim Demaille <akim.demaille@docker.com>
| static void log_flush(void) | ||
| { | ||
| buf[buf_idx] = 0; | ||
| asl_log(log_client, log_msg, ASL_LEVEL_NOTICE, "%s", buf); |
There was a problem hiding this comment.
If I read the code which calls this function correctly in neither case where this function is called do you null terminate buf. So, how does asl_log() know how long buf is?
There was a problem hiding this comment.
Hi @rn. Again, I'm sorry, but I don't know what you mean. It seems to me that you missed line 32, or I misunderstand your sentence.
There was a problem hiding this comment.
sorry, my bad. shouldn't review code without having coffee before
| if ((c == '\n') || (c == 0)) { | ||
| log_flush(); | ||
| } else { | ||
| if (buf_idx + 2 >= sizeof(buf)) { |
There was a problem hiding this comment.
this now seems to waste one byte.
But more in general maybe have something like:
if (buf_idx + 2 >= sizeof(buf)) {
buf[buf_idx++] = c;
c = 0;
}
if ((c == '\n') || (c == 0)) {
buf[buf_idx] = 0;
asl_log(log_client, log_msg, ASL_LEVEL_NOTICE, "%s", buf);
buf_idx = 0;
} else {
buf[buf_idx++] = c;
}
This would keep everything in one function, but it's a matter of taste, i guess
There was a problem hiding this comment.
Yes, I guess. I dislike assignments such as c=0 (I try to stick to some SSA form), and side-effects in expressions such as buf[buf_idx++]. Besides, in some situations you are now adding a \n (and 0) in the buffer.
|
Superseded by #199. |
On Docker for Windows, all the logs are aggregated: GUI, LinuxKit, etc. Currently debugging on Docker for Mac is less easy, in particular because LinuxKit's log are sent to the consolefile rings of fixed sized buffers. As a result, it's hard to find the interleaving of actions between Docker for Mac and LinuxKit, the console files are fragmented and full of '\0', etc.
It is possible to have hyperkit redirect the tty's output to its stderr/stdout, and then its caller (Docker for Mac's driver) could redirect these streams to the logger. Unfortunately on recent macOS releases, asl is replaced by os_log which uses exclusively the path of the executable as source (changing
argv[0]is useless). As a result, hyperkit's logs are "credited" to the driver.The most elegant approach is to embrace asl/os_log.
Currently Docker for Mac still targets 10.11, and os_log appeared in macOS 10.12, so we cannot use directly os_log's API, we use it through the asl shim.
Since some of the logs use escapes for the terminal, escapes appear in the logs. But that's another issue.