Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added unit section to status endpoint #928 #1089

Open
wants to merge 20 commits into
base: master
Choose a base branch
from

Conversation

Pavlusha311245
Copy link

@Pavlusha311245 Pavlusha311245 commented Jan 25, 2024

Added unit section to /status endpoint. Unit section is about web-server version, config last load time and config update generation

Response example below:

{
  "unit": {
    "version": "1.32.0",
    "load_time": "2024-01-25T13:24:08.000Z",
    "generation": 0
  },
  "connections": {
    "accepted": 0,
    "active": 0,
    "idle": 0,
    "closed": 0
  },
  "requests": {
    "total": 0
  },
  "applications": {
    "laravel": {
      "processes": {
        "running": 1,
        "starting": 0,
        "idle": 1
      },
      "requests": {
        "active": 0
      }
    }
  }
}

Closes: #928

@ac000
Copy link
Member

ac000 commented Jan 25, 2024

Hi @Pavlusha311245

Thanks for the PR!

OK, first things first, this is going to need a commit message.

What is this commit introducing and why do we want/need it. In this case perhaps also some example output from the endpoint.

Could you move the GH issue number from the commit subject and put it at the bottom of the commit message (separated by a blank line) in the form

Closes: https://github.com/nginx/unit/issues/928

Cheers,
Andrew

@Pavlusha311245
Copy link
Author

Pavlusha311245 commented Jan 25, 2024

@ac000 Do you need a commit message like this?

Add unit section to status endpoint

Added unit section to /status endpoint. Unit section is about web-server version, config last load time and config update generation
Closes: https://github.com/nginx/unit/issues/928

.gitignore Outdated
@@ -2,3 +2,4 @@
Makefile
*.pyc
__pycache__/
/.idea/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not entirely sure what thiis is. However I suspect this is better handled locally your end by either adding it into your global .gitignore or in the repository under .git/info/exclude

docs/changes.xml Outdated
Comment on lines 29 to 31
<change type="feature">
<para>
added unit section to /status endpoint.
unit section is about web-server version, config last load time and config update generation
</para>
</change>


<change type="feature">
<para>
$request_id variable contains a string that is formed using random data and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need two of these!?

docs/changes.xml Outdated
Comment on lines 174 to 171
date="2023-08-31" time="18:00:00 +0300"
packager="Nginx Packaging &lt;[email protected]&gt;">

<change type="feature">
<para>
added unit section to /status endpoint.
unit section is about web-server version, control_socket and etc.
</para>
</change>

<change type="change">
<para>
if building with njs, version 0.8.0 or later is now required.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here's the other one.

I would also prefer the changelog entry to be in its own commit. This can give things like git-revert(1) a better chance of working if we ever need to for some reason...

Comment on lines 2430 to 2434
nxt_buf_t *b;
nxt_port_t *main_port;
nxt_runtime_t *rt;
time_t rawtime;
struct tm *timeinfo;
u_char buffer[25];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variables should be sorted in christmias tree order and variable types of the same length should be sorted alphabetically, yeah, I know, I didn't make the rule...

Comment on lines 777 to 781
nxt_sockaddr_t *sa;
nxt_file_name_str_t file_name;
const nxt_event_interface_t *interface;
time_t rawtime;
struct tm *timeinfo;
u_char buffer[25];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto variables...

strftime((char*)buffer, 25, "%Y-%m-%dT%H:%M:%S.000Z", timeinfo);

rt->conf_ltime.length = strlen((char*)buffer);
rt->conf_ltime.start = (u_char*) malloc(rt->conf_ltime.length + 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't tend to call malloc(3) directly. There is a nxt_malloc() you can use.

Also please don't cast the return, in C it simply isn't required and (in that past at least) can hide bugs.

strftime((char*)buffer, 25, "%Y-%m-%dT%H:%M:%S.000Z", timeinfo);

rt->conf_ltime.length = strlen((char*)buffer);
rt->conf_ltime.start = (u_char*) malloc(rt->conf_ltime.length + 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Diit malloc(3).

Hmm, duplicated code...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ac000 How I can fix code duplicate? I'm using this code to get time and convert it to nxt_str_t. I can create nxt_time.c with function to reuse it

time_t         rawtime;
u_char         buffer[25];
struct         tm *timeinfo;

time(&rawtime);
timeinfo = gmtime(&rawtime);  //convert to UTC
strftime((char*)buffer, 25, "%Y-%m-%dT%H:%M:%S.000Z", timeinfo);

rt->conf_ltime.length = strlen((char*)buffer);
rt->conf_ltime.start = nxt_malloc(rt->conf_ltime.length + 1);

src/nxt_status.c Outdated
Comment on lines 22 to 28
rt = task->thread->runtime;

static nxt_str_t unit_str = nxt_string("unit");
static nxt_str_t ver_str = nxt_string("version");
static nxt_str_t gen_str = nxt_string("generation");
static nxt_str_t ltime_str = nxt_string("load_time");
static nxt_str_t conns_str = nxt_string("connections");
static nxt_str_t acc_str = nxt_string("accepted");
static nxt_str_t active_str = nxt_string("active");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable declarations should come before code... (I know there are some places that break this).

src/nxt_status.c Outdated
Comment on lines 14 to 20
size_t i;
nxt_str_t name;
nxt_str_t version;
nxt_int_t ret;
nxt_status_app_t *app;
nxt_conf_value_t *status, *obj, *apps, *app_obj;
nxt_runtime_t *rt;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto variables...

@ac000
Copy link
Member

ac000 commented Jan 25, 2024

@ac000 Do you need a commit message like this?

Hi.

Add unit section to status endpoint

Added unit section to /status endpoint. Unit section is about web-server version, config last load time and config update generation
Closes: https://github.com/nginx/unit/issues/928

Just about!. Something like this at the minimum

Add unit section to status endpoint                                             
                                                                                
Added unit section to /status endpoint. Unit section is about web-server        
version, config last load time and config update generation.                    
                                                                                
<Insert example output from /endpoint here...>                                  
                                                                                
Closes: https://github.com/nginx/unit/issues/928

Wrapping lines around the 72/73 charcter mark.

Thanks!

@ac000
Copy link
Member

ac000 commented Jan 25, 2024

Thanks for that, quick couple of questions.

"unit": {
    "version": "1.32.0",
    "load_time": "2024-01-25T13:24:08.000Z",
    "generation": 0
  },

Is load_time when unit was started or last configured?

What does generation represent?

@Pavlusha311245
Copy link
Author

Thanks for that, quick couple of questions.

"unit": {
    "version": "1.32.0",
    "load_time": "2024-01-25T13:24:08.000Z",
    "generation": 0
  },

Is load_time when unit was started or last configured?

What does generation represent?

load_time shows when last configured
generation represents count of config updates. When you update listeners, applications and etc.

@Pavlusha311245
Copy link
Author

Some changes are ready after review. I will push it asap

@Pavlusha311245 Pavlusha311245 force-pushed the feature/unit_about_section branch from 2e85ebd to 9611a3b Compare January 25, 2024 15:51
@Pavlusha311245
Copy link
Author

Pavlusha311245 commented Jan 25, 2024

@ac000 Can you help me with changes.xml or I can push my version with new commit?

@ac000
Copy link
Member

ac000 commented Jan 25, 2024

If you have it split out locally, you can force push it here with git push -f ...

@Pavlusha311245
Copy link
Author

Pavlusha311245 commented Jan 25, 2024

And here's the other one.

I would also prefer the changelog entry to be in its own commit. This can give things like git-revert(1) a better chance of working if we ever need to for some reason...

From review context

Pardon. Should I merge with commit and push or push with new commit?

@ac000
Copy link
Member

ac000 commented Jan 25, 2024

After you split the changelog changes into their own commit, you should then have two commits for this PR, at which point you can force push them to your feature/unit_about_section branch.

@ac000
Copy link
Member

ac000 commented Jan 25, 2024

Thanks for that, quick couple of questions.

"unit": {
    "version": "1.32.0",
    "load_time": "2024-01-25T13:24:08.000Z",
    "generation": 0
  },

load_time shows when last configured generation represents count of config updates. When you update listeners, applications and etc.

Hmm, we probably need a better name for this. 'last_configured' or somesuch...

Although a 'start_time' would perhaps be useful.

@Pavlusha311245
Copy link
Author

Pavlusha311245 commented Jan 25, 2024

I can add it. Ask me if you know how to name it better. Do I need add start_time?

strftime((char*)buffer, 25, "%Y-%m-%dT%H:%M:%S.000Z", timeinfo);

rt->conf_ltime.length = strlen((char*)buffer);
rt->conf_ltime.start = nxt_malloc(rt->conf_ltime.length + 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for changing the malloc(3), but now my next question is, where does this memory get free(3)'d?.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know when memory should be released. Can you explain me?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually use nxt_mp_xxx api instead that you don't need to explicitly do memory free.
It will be released together with the mp destroy.

rt->conf_ltime.length = strlen((char*)buffer);
rt->conf_ltime.start = nxt_malloc(rt->conf_ltime.length + 1);

strcpy((char*)rt->conf_ltime.start, (char*)buffer);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be nxt_copystr()

@ac000
Copy link
Member

ac000 commented Jan 25, 2024

I can add it. Ask me if you know how to name it better. Do I need add start_time?

My current suggestion is 'load_time' -> 'last_configured' but maybe others will have better suggestions. Don't worry about that one for now...

Added unit section to /status endpoint. Unit section is about web-server version, config last load time and config update generation
Response example below:
{"unit":{"version":"1.32.0","load_time":"2024-01-25T13:24:08.000Z","generation":0},"connections":{"accepted":0,"active":0,"idle":0,"closed":0},"requests":{"total":0},"applications":{"laravel":{"processes":{"running":1,"starting":0,"idle":1},"requests":{"active":0}}}}
Closes: nginx#928
@Pavlusha311245 Pavlusha311245 force-pushed the feature/unit_about_section branch from 9611a3b to 2cb99ca Compare January 26, 2024 09:25
@hongzhidao
Copy link
Contributor

@hongzhidao
Copy link
Contributor

I can add it. Ask me if you know how to name it better. Do I need add start_time?

My current suggestion is 'load_time' -> 'last_configured' but maybe others will have better suggestions. Don't worry about that one for now...

It looks good to contain config for the option.

@hongzhidao
Copy link
Contributor

andrey-zelenkov and others added 8 commits January 26, 2024 15:17
According to the Node.js documenation this variable
should only include numbering scheme.

Thanks to @dbit-xia.

Closes: nginx#1085
This is in preparation for adding conditional access logging.
No functional changes.
This feature allows users to specify conditions to control if access log
should be recorded. The "if" option supports a string and JavaScript code.
If its value is empty, 0, false, null, or undefined, the logs will not be
recorded. And the '!' as a prefix inverses the condition.

Example 1: Only log requests that sent a session cookie.

    {
        "access_log": {
            "if": "$cookie_session",
            "path": "..."
        }
    }

Example 2: Do not log health check requests.

    {
        "access_log": {
            "if": "`${uri == '/health' ? false : true}`",
            "path": "..."
        }
    }

Example 3: Only log requests when the time is before 22:00.

    {
        "access_log": {
            "if": "`${new Date().getHours() < 22}`",
            "path": "..."
        }
    }

or

    {
        "access_log": {
            "if": "!`${new Date().getHours() >= 22}`",
            "path": "..."
        }
    }

Closes: nginx#594
Conditional access logging was introduced here:
nginx@4c91beb
This is a generic type to represent a uid_t/gid_t on Linux when user
namespaces are in use.

Technically this only needs to be an unsigned int, but we make it an
int64_t so we can make use of the existing NXT_CONF_MAP_INT64 type.

This will be used in subsequent commits.

Reviewed-by: Zhidao Hong <[email protected]>
Signed-off-by: Andrew Clayton <[email protected]>
Andrei reported an issue on arm64 where he was seeing the following
error message when running the tests

  2024/01/17 18:32:31.109 [error] 54904#54904 "gidmap" field has an entry with "size": 1, but for unprivileged unit it must be 1.

This error message is guarded by the following if statement

  if (nxt_slow_path(m.size > 1)

Turns out size was indeed > 1, in this case it was 289356276058554369,
m.size is defined as a nxt_int_t, which on arm64 is actually 8 bytes,
but was being printed as a signed int (4 bytes) and by chance/undefined
behaviour comes out as 1.

But why is size so big? In this case it should have just been 1 with a
config of

  'gidmap': [{'container': 0, 'host': os.getegid(), 'size': 1}],

This is due to nxt_int_t being 64bits on arm64 but using a conf type of
NXT_CONF_MAP_INT which means in nxt_conf_map_object() we would do (using
our m.size variable as an example)

  ptr = nxt_pointer_to(data, map[i].offset);
  ...
  ptr->i = num;

Where ptr is a union pointer and is now pointing at our m.size

Next we set m.size to the value of num (which is 1 in this case), via
ptr->i where i is a member of that union of type int.

So here we are setting a 64bit memory location (nxt_int_t on arm64)
through a 32bit (int) union alias, this means we are only setting the
lower half (4) of the bytes.

Whatever happens to be in the upper 4 bytes will remain, giving us our
exceptionally large value.

This is demonstrated by this program

  #include <stdio.h>
  #include <stdint.h>

  int main(void)
  {
          int64_t num = -1; /* All 1's in two's complement */
          union {
                  int32_t i32;
                  int64_t i64;
          } *ptr;

          ptr = (void *)&num;

          ptr->i32 = 1;
          printf("num : %lu / %ld\n", num, num);
          ptr->i64 = 1;
          printf("num : %ld\n", num);

          return 0;
  }
  $ make union-32-64-issue
  cc     union-32-64-issue.c   -o union-32-64-issue
  $ ./union-32-64-issue
  num : 18446744069414584321 / -4294967295
  num : 1

However that is not the only issue, because the members of
nxt_clone_map_entry_t were specified as nxt_int_t's on the likes of
x86_64 this would be a 32bit signed integer. However uid/gids on Linux
at least are defined as unsigned integers, so a nxt_int_t would not be
big enough to hold all potential values.

We could make the nxt_uint_t's but then we're back to the above union
aliasing problem.

We could just set the memory for these variables to 0 and that would
work, however that's really just papering over the problem.

The right thing is to use a large enough sized type to store these
things, hence the previously introduced nxt_cred_t. This is an int64_t
which is plenty large enough.

So we switch the nxt_clone_map_entry_t structure members over to
nxt_cred_t's and use NXT_CONF_MAP_INT64 as the conf type, which then
uses the right sized union member in nxt_conf_map_object() to set these
variables.

Reported-by: Andrei Zeliankou <[email protected]>
Reviewed-by: Zhidao Hong <[email protected]>
Signed-off-by: Andrew Clayton <[email protected]>
Use the NXT_CONF_VLDT_REQUIRED flag on the app_procmap members. These
three settings are required.

These are for the uidmap & gidmap settings in the config.

Suggested-by: Zhidao HONG <[email protected]>
Reviewed-by: Zhidao Hong <[email protected]>
Signed-off-by: Andrew Clayton <[email protected]>
alejandro-colomar and others added 7 commits February 5, 2024 18:37
The commit that added support for Unix sockets accepts abstract sockets
using '@' in the config, but we stored it internally using '\0'.

We want to support abstract sockets transparently to the user, so that
if the user configures unitd with '@', if we receive a query about the
current configuration, the user should see the same exact thing that was
configured.  So, this commit avoids the transformation in the internal
state file, storing user input pristine, and we only transform the '@'
in temporary strings.

This commit fixes another bug, where we try to connect to abstract
sockets with a trailing '\0' in their name due to calling twice
nxt_sockaddr_parse() on the same string.  By calling that function only
once with each copy of the string, we have fixed that bug.

The following code was responsible for this bug, which the second time
it was called, considered these sockets as file-backed (not abstract)
Unix socket, and so appended a '\0' to the socket name.

    $ grepc -tfd nxt_sockaddr_unix_parse . | grep -A10 @
        if (path[0] == '@') {
            path[0] = '\0';
            socklen--;
    #if !(NXT_LINUX)
            nxt_thread_log_error(NXT_LOG_ERR,
                                 "abstract unix domain sockets are not supported");
            return NULL;
    #endif
        }

        sa = nxt_sockaddr_alloc(mp, socklen, addr->length);

This bug was found thanks to some experiment about using 'const' for
some strings.

And here's some history:

-  9041d27 ("nxt_sockaddr_parse() introducted.")

   This commit introduced support for abstract Unix sockets, but they
   only worked as "servers", and not as "listeners".  We corrupted the
   JSON config file, and stored a \u0000.  This also caused calling
   connect(2) with a bogus trailing null byte, which tried to connect to
   a different abstract socket.

-  d8e0768 ("Fixed support for abstract Unix sockets.")

   This commit (partially) fixed support for abstract Unix sockets, so
   they they worked also as listeners.  We still corrupted the JSON
   config file, and stored a \u0000.  This caused calling connect(2)
   (and now bind(2) too) with a bogus trailing null byte.

-  e2aec66 ("Storing abstract sockets with @ internally.")

   This commit fixed the problem by which we were corrupting the config
   file, but only for "listeners", not for "servers".  (It also fixes
   the issue about the terminating '\0'.)  We completely forgot about
   "servers", and other callers of the same function.

To reproduce the problem, I used the following config:

```json
{
	"listeners": {
		"*:80": {
			"pass": "routes/u"
		},
		"unix:@abstract": {
			"pass": "routes/a"
		}
	},

	"routes": {
		"u": [{
			"action": {
				"pass": "upstreams/u"
			}
		}],
		"a": [{
			"action": {
				"return": 302,
				"location": "/i/am/not/at/home/"
			}
		}]
	},

	"upstreams": {
		"u": {
			"servers": {
				"unix:@abstract": {}
			}
		}
	}
}
```

And then check the state file:

    $ sudo cat /opt/local/nginx/unit/master/var/lib/unit/conf.json \
    | jq . \
    | grep unix;
        "unix:@abstract": {
            "unix:\u0000abstract": {}

After this patch, the state file has a '@' as expected:

    $ sudo cat /opt/local/nginx/unit/unix/var/lib/unit/conf.json \
    | jq . \
    | grep unix;
        "unix:@abstract": {
            "unix:@abstract": {}

Regarding the trailing null byte, here are some tests:

    $ sudo strace -f -e 'bind,connect' /opt/local/nginx/unit/d8e0/sbin/unitd \
    |& grep abstract;
    [pid 22406] bind(10, {sa_family=AF_UNIX, sun_path=@"abstract\0"}, 12) = 0
    [pid 22410] connect(134, {sa_family=AF_UNIX, sun_path=@"abstract\0"}, 12) = 0
    ^C
    $ sudo killall unitd
    $ sudo strace -f -e 'bind,connect' /opt/local/nginx/unit/master/sbin/unitd \
    |& grep abstract;
    [pid 22449] bind(10, {sa_family=AF_UNIX, sun_path=@"abstract"}, 11) = 0
    [pid 22453] connect(134, {sa_family=AF_UNIX, sun_path=@"abstract\0"}, 12) = -1 ECONNREFUSED (Connection refused)
    ^C
    $ sudo killall unitd
    $ sudo strace -f -e 'bind,connect' /opt/local/nginx/unit/unix/sbin/unitd \
    |& grep abstract;
    [pid 22488] bind(10, {sa_family=AF_UNIX, sun_path=@"abstract"}, 11) = 0
    [pid 22492] connect(134, {sa_family=AF_UNIX, sun_path=@"abstract"}, 11) = 0
    ^C

Fixes: 9041d27 ("nxt_sockaddr_parse() introducted.")
Fixes: d8e0768 ("Fixed support for abstract Unix sockets.")
Fixes: e2aec66 ("Storing abstract sockets with @ internally.")
Link: <nginx#1108>
Reviewed-by: Andrew Clayton <[email protected]>
Cc: Liam Crilly <[email protected]>
Cc: Zhidao Hong <[email protected]>
Signed-off-by: Alejandro Colomar <[email protected]>
It's an integer, not a floating number.

Fixes: 68c6b67 ("Configuration: support for rational numbers.")
Closes: nginx#1115
Link: <nginx#1116>
Reviewed-by: Zhidao Hong <[email protected]>
Reviewed-by: Andrew Clayton <[email protected]>
Cc: Dan Callahan <[email protected]>
Cc: Valentin Bartenev <[email protected]>
Signed-off-by: Alejandro Colomar <[email protected]>
Added unit section to /status endpoint. Unit section is about web-server version, config last load time and config update generation
Response example below:
{"unit":{"version":"1.32.0","load_time":"2024-01-25T13:24:08.000Z","generation":0},"connections":{"accepted":0,"active":0,"idle":0,"closed":0},"requests":{"total":0},"applications":{"laravel":{"processes":{"running":1,"starting":0,"idle":1},"requests":{"active":0}}}}
Closes: nginx#928
@Pavlusha311245
Copy link
Author

Hi @hongzhidao I can start working on this pr again. Can you describe in more detail what you meant by giving links to methods? Is there a problem with the formatting or the coding itself?

@hongzhidao
Copy link
Contributor

Is there a problem with the formatting or the coding itself?

   time(&rawtime);
    timeinfo = gmtime(&rawtime);  //convert to UTC
    strftime((char*)buffer, 25, "%Y-%m-%dT%H:%M:%S.000Z", timeinfo);

I mean there is nxt_time_string_t in Unit, maybe you can use it instead of calling strftime().

@Pavlusha311245
Copy link
Author

Is this all the comments on the code for now? I would like to know if there is anything else, then I can fully understand what to do so as not to return to the dialogue after each completed stage 🙂

@Pavlusha311245
Copy link
Author

Is there a problem with the formatting or the coding itself?

   time(&rawtime);
    timeinfo = gmtime(&rawtime);  //convert to UTC
    strftime((char*)buffer, 25, "%Y-%m-%dT%H:%M:%S.000Z", timeinfo);

I mean there is nxt_time_string_t in Unit, maybe you can use it instead of calling strftime().

But... How to use nxt_time_strint_t? I use function nxt_conf_set_member_string in nxt_status to show data

@hongzhidao
Copy link
Contributor

Is this all the comments on the code for now?

I'm afraid we can only point out that these are obvious first. Not sure if there is anyone else. And in our experience, it's common to rework patches after review.

How to use nxt_time_strint_t?

I'd suggest you look into the source code, it's too detailed, but I usually look at its usage by doing something like grep nxt_time_strint_t.

@ac000
Copy link
Member

ac000 commented Mar 19, 2024

Hi @hongzhidao

Can a nxt_time_string_t store an arbitrary time?

It's not immediately obvious to me as the handler function seems to
basically take a struct timespec and struct tm representing the current
time.

@hongzhidao
Copy link
Contributor

Can a nxt_time_string_t store an arbitrary time?

Yes, I think so.

Here's an example from nxt_app_log.c:

static nxt_time_string_t  nxt_log_debug_time_cache = {
    (nxt_atomic_uint_t) -1,
    nxt_log_debug_time,
    "%4d/%02d/%02d %02d:%02d:%02d.%03d ",
    nxt_length("1970/09/28 12:00:00.000 "),
    NXT_THREAD_TIME_LOCAL,
    NXT_THREAD_TIME_MSEC,
};


static u_char *
nxt_log_debug_time(u_char *buf, nxt_realtime_t *now, struct tm *tm, size_t size,
    const char *format)
{
    return nxt_sprintf(buf, buf + size, format,
                       tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
                       tm->tm_hour, tm->tm_min, tm->tm_sec,
                       now->nsec / 1000000);
}


demo()
{
   nxt_time_string_t  *time_cache;
   u_char             msg[NXT_MAX_ERROR_STR];

    thr = nxt_thread();

    time_cache = &nxt_log_debug_time_cache;

    p = nxt_thread_time_string(thr, time_cache, msg);
}

basically take a struct timespec and struct tm representing the current
time.

Correct. And nxt_thread_time_string() handles the cached current time well.
I think we can use it to represent string-time like the structure nxt_time_string_t naming.

The other example is related to http variable.

static nxt_int_t
nxt_http_var_time_local(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data)
{
    nxt_http_request_t  *r;

    static nxt_time_string_t  date_cache = {
        (nxt_atomic_uint_t) -1,
        nxt_http_log_date,
        "%02d/%s/%4d:%02d:%02d:%02d %c%02d%02d",
        nxt_length("31/Dec/1986:19:40:00 +0300"),
        NXT_THREAD_TIME_LOCAL,
        NXT_THREAD_TIME_SEC,
    };
"src/nxt_http_variables.c" line 344 of 778

@hongzhidao
Copy link
Contributor

Hi @Pavlusha311245,
I'd suggest you change nxt_http_var_time_local() to meet the new string format and you can test it with access log.
After you check it works, you can do it in your way with nxt_time_string_t.

@ac000
Copy link
Member

ac000 commented Mar 20, 2024

@hongzhidao

It's a convoluted interface, but I see how it would work now.

However I think it could end up doing quite a lot of work at start up
that may never be used (e.g if /status is never looked at).

At least I think it might be best to simply store either a time_t or
struct timespec (depending on required resolution) and then use that
at the time of showing /status.

That's the bit I'm not sure about, if you can use an arbitrary struct timespec with nxt_time_string_t?

Failing that just use the standard libc API at the time of showing
/status

That way you do less work at start up (only storing a time_t or struct timepsec)

You also use a little less memory for each timestamp, 8 or 16 bytes vs
25 or whatever.

@ac000
Copy link
Member

ac000 commented Mar 20, 2024

@hongzhidao

I'd suggest you change nxt_http_var_time_local() to meet the new
string format and you can test it with access log.

Are you suggesting to change the format of a timestamp in the
access_log?

If so, I'm not sure that's a good idea, people may be relying on the
existing format...

@hongzhidao
Copy link
Contributor

Are you suggesting to change the format of a timestamp in the
access_log?

Nope, it's for learning the usage of nxt_time_string_t for anyone who is not familiar with it. It's a test to do it easily.
It's ok for him to write a new one directly like this requirement.

@hongzhidao
Copy link
Contributor

That's the bit I'm not sure about, if you can use an arbitrary struct timespec with nxt_time_string_t?

I think so. Here's the related source code.
https://github.com/nginx/unit/blob/master/src/nxt_time.c#L25

At least I think it might be best to simply store either a time_t or
struct timespec (depending on required resolution) and then use that
at the time of showing /status.

Sorry that I don't understand it.

@hongzhidao
Copy link
Contributor

Hi @lcrilly,
We could consider supporting this variable. It's a bit to do with this requirement. Both of them have the same time format.
#1118

@ac000
Copy link
Member

ac000 commented Mar 20, 2024

That's the bit I'm not sure about, if you can use an arbitrary
struct timespec with nxt_time_string_t?

I think so. Here's the related source code.
https://github.com/nginx/unit/blob/master/src/nxt_time.c#L25

Yes, but how do you get that into the nxt_time_string_t handler
function?

The handler function is called from the likes of
nxt_thread_time_string which uses the current time.

So I can't see how you could store a struct timespec and then at some
point later use it with nxt_time_string_t...

Looks like you'd need to create a new function for that case...

At least I think it might be best to simply store either a time_t
or struct timespec (depending on required resolution) and then use
that at the time of showing /status

Sorry that I don't understand it.

Rather than storing a string representing the timestamp, you simply store
a time_t or struct timespec instead, saving memory and then you only
do the conversion into a string when required.

But I suppose it's a toss up between doing perhaps unnecessary work at
startup and how often /status is called...

@hongzhidao
Copy link
Contributor

hongzhidao commented Mar 20, 2024

So I can't see how you could store a struct timespec and then at some
point later use it with nxt_time_string_t...
Looks like you'd need to create a new function for that case...

Yes, you need a specific function, for example.

  1. Defined a variable of nxt_time_string_t.
static nxt_time_string_t  date_cache = {
        (nxt_atomic_uint_t) -1,
        nxt_http_log_date,
        "%02d/%s/%4d:%02d:%02d:%02d %c%02d%02d",
        nxt_length("31/Dec/1986:19:40:00 +0300"),
        NXT_THREAD_TIME_LOCAL,
        NXT_THREAD_TIME_SEC,
    };

Note that the function nxt_http_log_date should be implemented later, it's to format the string time.

nxt_http_log_date(u_char *buf, nxt_realtime_t *now, struct tm *tm,
    size_t size, const char *format)
{
    ...
    return nxt_sprintf(buf, buf + size, format,
                       tm->tm_mday, month[tm->tm_mon], tm->tm_year + 1900,
                       tm->tm_hour, tm->tm_min, tm->tm_sec,

  1. Called nxt_thread_time_string() to generate the time string.
str->length = nxt_thread_time_string(task->thread, &date_cache, str->start)
                  - str->start;

Rather than storing a string representing the timestamp, you simply store
a time_t or struct timespec instead,

There are serval ways to do it like what happens in nxt_time.c.

nxt_realtime(nxt_realtime_t *now);
void nxt_localtime(nxt_time_t s, struct tm *tm);

But I suppose it's a toss up between doing perhaps unnecessary work at
startup and how often /status is called...

I guess it's not often, but anyway, I feel nxt_thread_time_string() is more efficient since it has cached value internally.
My thought is to reuse the time and time-string infrastructure as much as possible.

@ac000
Copy link
Member

ac000 commented Mar 20, 2024

│ So I can't see how you could store a struct timespec and then at some
│ point later use it with nxt_time_string_t...
│ Looks like you'd need to create a new function for that case...

Yes, you need a specific function, for example.

[...]

  1. Called nxt_thread_time_string() to generate the time string.

str->length = nxt_thread_time_string(task->thread, &date_cache, str->start)
- str->start;

My point is these all operate on the current time. So you'd need a
new function that takes an existing struct timespec.

│ Rather than storing a string representing the timestamp, you simply
│ store a time_t or struct timespec instead.

There are serval ways to do it like what happens in nxt_time.c .

nxt_realtime(nxt_realtime_t *now);
void nxt_localtime(nxt_time_t s, struct tm *tm);

I wasn't asking how to do it, I was suggesting it may be a preferable
way to do it...

│ But I suppose it's a toss up between doing perhaps unnecessary work
│ at startup and how often /status is called...

I guess it's not often, but anyway, I feel nxt_thread_time_string()
is more efficient since it has cached value internally.

What is cached exactly?

I think there's a lot over-optimisation in Unit...

However reading the clock should generally be pretty efficient as it's
usually handled via a vDSO(7).

My thought is to reuse the time and time-string infrastructure as much
as possible.

Where it makes sense...

@hongzhidao
Copy link
Contributor

hongzhidao commented Mar 21, 2024

What is cached exactly?
I think there's a lot over-optimisation in Unit...

Take the error log as an example, if there are lots of logs generated in a very short term, for example, 1s, these logs may count the time only once, but not calling something like gmtime() and strftime() for each logging.
It has a cached time-string value for each nxt_time_string_t variable.

However reading the clock should generally be pretty efficient as it's
usually handled via a vDSO(7).

Good to know, thanks.

Either way, we have to do two things: get the current time and format time-string.
In nxt_time.c, it has the APIs to get the current time, and it's used in nxt_thread_time_string().
Then we usually use nxt_sprintf() to format the string.

Comment on lines +76 to +77
nxt_int_t conf_gen;
nxt_str_t conf_ltime;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Pavlusha311245

There is a nxt_status_report_t structure in src/nxt_status.h. I wonder if these would be better placed there?

Copy link
Author

@Pavlusha311245 Pavlusha311245 Mar 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Pavlusha311245

There is a nxt_status_report_t structure in src/nxt_status.h. I wonder if these would be better placed there?

Hmm... I can try 🙂 But I thought of leaving them in runtime so that I could use them anywhere else. Also I need it here to known time when config initialized. Have you suggestion how I can do it? @ac000

Copy link
Contributor

@hongzhidao hongzhidao Mar 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems they don't belong to nxt_runtime_t and nxt_status_report_t.
The nxt_status_report_t is a temporary structure to collect the status information from every engine between worker threads in the router process.
The configuration generation needs to increase when a new configuration is generated. But I can't see where it happens in the PR. The configuration time needs to be recorded on its corresponding configuration.
I feel you need to handle them first.
No clear idea on it yet, but you might start with nxt_router_conf_data_handler() which generates configuration structure through control API.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The configuration generation needs to increase when a new configuration is generated. But I can't see where it happens in the PR. The configuration time needs to be recorded on its corresponding configuration. I feel you need to handle them first.

The update of the counter and time is now located in nxt_controller in thenxt_controller_conf_store() method

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The configuration is updated by the control API which is created in the controller process.
I feel it has nothing to do with the structure nxt_runtime_t.
When a new configuration is applied, the last JSON value is stored as nxt_controller_conf.
We could consider putting the generation and updated time in this structure nxt_controller_conf_t.
Btw, debug information in error.log is useful.

@ac000
Copy link
Member

ac000 commented Mar 21, 2024 via email

@Pavlusha311245
Copy link
Author

Do you have a specific use case in mind? I think it would be nice to have all (or at least most of them, I can see the odd one possibly coming from some other pre-existing place) the status variables stored in the same place. If you did then find that you needed them for some other purpose, then I would look at making the status structure more accessible. I know this may sound like a lot of extra work, but I think it's important to get the fundamentals right now, before this API is expanded any further...

@ac000 Hi. Unfortunately, I can’t think of any case, although I think it might suddenly be needed. As I understand from the message. If I need to save a given use case, will I need to make a structure for this?

@avahahn
Copy link
Contributor

avahahn commented Jun 17, 2024

@Pavlusha311245 Hello there!
I just wanted to reach out and see if you were still engaged in pushing this changeset forward... It seems like good information to be exposing, and I wanted to check up on how this is going.

If I understand correctly it looks like left to do is to use the nxt_mp_alloc instead of nxt_alloc and to refactor to extend the nxt_controller module instead of keeping the new data in the nxt_runtime_t object?

Please let us know if there is anything blocking you!

@Pavlusha311245
Copy link
Author

@Pavlusha311245 Hello there! I just wanted to reach out and see if you were still engaged in pushing this changeset forward... It seems like good information to be exposing, and I wanted to check up on how this is going.

If I understand correctly it looks like left to do is to use the nxt_mp_alloc instead of nxt_alloc and to refactor to extend the nxt_controller module instead of keeping the new data in the nxt_runtime_t object?

Please let us know if there is anything blocking you!

There is not enough time to complete this task. I want to close it as soon as possible so that the changes go to the main branch. If there is such a possibility, I would ask for help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Enhancement: Nginx Unit endpoint /about
7 participants